Prevent chat and other player rendering race conditions when logging in at the same time

This commit is contained in:
bdnugget 2025-04-16 12:28:30 +02:00
parent 62a6bb2926
commit 541f53c06a
3 changed files with 133 additions and 37 deletions

View File

@ -59,6 +59,12 @@ func (c *Chat) HandleServerMessages(messages []*pb.ChatMessage) {
// Convert protobuf messages to our local type // Convert protobuf messages to our local type
for _, msg := range messages { for _, msg := range messages {
// Skip invalid messages
if msg == nil {
log.Printf("Warning: Received nil chat message")
continue
}
localMsg := types.ChatMessage{ localMsg := types.ChatMessage{
PlayerID: msg.PlayerId, PlayerID: msg.PlayerId,
Username: msg.Username, Username: msg.Username,
@ -81,24 +87,41 @@ func (c *Chat) HandleServerMessages(messages []*pb.ChatMessage) {
} }
// Add floating message to the player // Add floating message to the player
if game, ok := c.userData.(*Game); ok { if game, ok := c.userData.(*Game); ok && game != nil {
if msg.PlayerId == game.Player.ID { // Make sure each game component exists before using it
game.Player.Lock() if game.PlayerManager == nil {
game.Player.FloatingMessage = &types.FloatingMessage{ 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, Content: msg.Content,
ExpireTime: time.Now().Add(6 * time.Second), ExpireTime: time.Now().Add(6 * time.Second),
} }
game.Player.Unlock() game.PlayerManager.LocalPlayer.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)
} else { } 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)
} }
} }
} }

View File

@ -53,6 +53,8 @@ func New() *Game {
Projection: rl.CameraPerspective, Projection: rl.CameraPerspective,
}, },
quitChan: make(chan struct{}), quitChan: make(chan struct{}),
// Initialize empty maps to avoid nil references
OtherPlayers: make(map[int32]*types.Player),
} }
// Initialize legacy fields (for backward compatibility) // Initialize legacy fields (for backward compatibility)
@ -119,18 +121,30 @@ func (g *Game) Update(deltaTime float32) {
g.PlayerManager.LocalPlayer = &types.Player{ g.PlayerManager.LocalPlayer = &types.Player{
Speed: 50.0, Speed: 50.0,
TargetPath: []types.Tile{}, TargetPath: []types.Tile{},
UserData: g, ActionQueue: []*pb.Action{},
QuitDone: make(chan struct{}), QuitDone: make(chan struct{}),
ID: playerID, ID: playerID,
} }
g.AssignModelToPlayer(g.PlayerManager.LocalPlayer) 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) go network.HandleServerCommunication(conn, playerID, g.PlayerManager.LocalPlayer, g.PlayerManager.OtherPlayers, g.quitChan)
g.UIManager.IsLoggedIn = true g.UIManager.IsLoggedIn = true
} }
return 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 // Handle ESC for menu
if rl.IsKeyPressed(rl.KeyEscape) { if rl.IsKeyPressed(rl.KeyEscape) {
g.UIManager.MenuOpen = !g.UIManager.MenuOpen g.UIManager.MenuOpen = !g.UIManager.MenuOpen
@ -142,6 +156,7 @@ func (g *Game) Update(deltaTime float32) {
return return
} }
// Handle chat updates
if message, sent := g.UIManager.Chat.Update(); sent { if message, sent := g.UIManager.Chat.Update(); sent {
g.PlayerManager.LocalPlayer.Lock() g.PlayerManager.LocalPlayer.Lock()
g.PlayerManager.LocalPlayer.ActionQueue = append(g.PlayerManager.LocalPlayer.ActionQueue, &pb.Action{ 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() g.PlayerManager.LocalPlayer.Unlock()
} }
// Process player input
g.HandleInput() 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()) 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 { if g.frameCounter%300 == 0 {
rl.TraceLog(rl.LogInfo, "There are %d other players", len(g.PlayerManager.OtherPlayers)) rl.TraceLog(rl.LogInfo, "There are %d other players", len(g.PlayerManager.OtherPlayers))
for id, other := range g.PlayerManager.OtherPlayers { for id, other := range g.PlayerManager.OtherPlayers {
if other != nil {
rl.TraceLog(rl.LogInfo, "Other player ID: %d, Position: (%f, %f, %f), Has model: %v", 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) 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 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 { if other.Model.Meshes == nil {
g.AssignModelToPlayer(other) 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) UpdateCamera(&g.Camera, g.PlayerManager.LocalPlayer.PosActual, deltaTime)
// Update music if available // Update music if available
@ -502,7 +524,12 @@ func (g *Game) Shutdown() {
} }
func (g *Game) HandleServerMessages(messages []*pb.ChatMessage) { func (g *Game) HandleServerMessages(messages []*pb.ChatMessage) {
// Check if Chat is properly initialized
if g.UIManager != nil && g.UIManager.Chat != nil {
g.UIManager.Chat.HandleServerMessages(messages) g.UIManager.Chat.HandleServerMessages(messages)
} else {
log.Printf("Warning: Cannot handle server messages, Chat is not initialized")
}
} }
func (g *Game) AssignModelToPlayer(player *types.Player) { func (g *Game) AssignModelToPlayer(player *types.Player) {

View File

@ -91,6 +91,22 @@ func (mh *MessageHandler) WriteMessage(msg proto.Message) error {
// UpdateGameState processes a server message and updates game state // UpdateGameState processes a server message and updates game state
func UpdateGameState(serverMessage *pb.ServerMessage, player *types.Player, otherPlayers map[int32]*types.Player) { 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 playerID := player.ID
player.Lock() player.Lock()
@ -99,7 +115,7 @@ func UpdateGameState(serverMessage *pb.ServerMessage, player *types.Player, othe
tickDiff := serverMessage.CurrentTick - player.CurrentTick tickDiff := serverMessage.CurrentTick - player.CurrentTick
if tickDiff > types.MaxTickDesync { if tickDiff > types.MaxTickDesync {
for _, state := range serverMessage.Players { for _, state := range serverMessage.Players {
if state.PlayerId == playerID { if state != nil && state.PlayerId == playerID {
player.ForceResync(state) player.ForceResync(state)
break break
} }
@ -110,6 +126,12 @@ func UpdateGameState(serverMessage *pb.ServerMessage, player *types.Player, othe
// Process player states // Process player states
validPlayerIds := make(map[int32]bool) validPlayerIds := make(map[int32]bool)
for _, state := range serverMessage.Players { for _, state := range serverMessage.Players {
// Skip invalid player states
if state == nil {
log.Printf("Warning: Received nil player state")
continue
}
validPlayerIds[state.PlayerId] = true validPlayerIds[state.PlayerId] = true
if state.PlayerId == playerID { if state.PlayerId == playerID {
@ -129,7 +151,13 @@ func UpdateGameState(serverMessage *pb.ServerMessage, player *types.Player, othe
// Update or create other players // Update or create other players
if otherPlayer, exists := otherPlayers[state.PlayerId]; exists { if otherPlayer, exists := otherPlayers[state.PlayerId]; exists {
if otherPlayer != nil {
otherPlayer.UpdatePosition(state, types.ServerTickRate) 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 { } else {
log.Printf("Creating new player with ID: %d", state.PlayerId) log.Printf("Creating new player with ID: %d", state.PlayerId)
otherPlayers[state.PlayerId] = types.NewPlayer(state) otherPlayers[state.PlayerId] = types.NewPlayer(state)
@ -144,14 +172,32 @@ func UpdateGameState(serverMessage *pb.ServerMessage, player *types.Player, othe
} }
} }
// Handle chat messages // Handle chat messages with safety checks
if handler, ok := player.UserData.(types.ChatMessageHandler); ok && len(serverMessage.ChatMessages) > 0 { 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)) log.Printf("Received %d chat messages from server", len(serverMessage.ChatMessages))
handler.HandleServerMessages(serverMessage.ChatMessages)
// 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 // Update the last seen message timestamp to the most recent message
if len(serverMessage.ChatMessages) > 0 { lastMsg := validMessages[len(validMessages)-1]
lastMsg := serverMessage.ChatMessages[len(serverMessage.ChatMessages)-1]
lastSeenMessageTimestamp = lastMsg.Timestamp lastSeenMessageTimestamp = lastMsg.Timestamp
log.Printf("Updated last seen message timestamp to %d", lastSeenMessageTimestamp) log.Printf("Updated last seen message timestamp to %d", lastSeenMessageTimestamp)
} }