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
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)
}
}
}

View File

@ -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) {

View File

@ -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)
}