add a bunch of mutexes and shit but still have racist conditions and panics LOL

This commit is contained in:
2025-01-22 20:14:58 +01:00
parent 417bf4ea63
commit 220a451475
7 changed files with 309 additions and 161 deletions

View File

@ -7,6 +7,7 @@ import (
"io"
"log"
"net"
"sync"
"time"
"gitea.boner.be/bdnugget/goonscape/types"
@ -91,19 +92,32 @@ func ConnectToServer(username, password string, isRegistering bool) (net.Conn, i
func HandleServerCommunication(conn net.Conn, playerID int32, player *types.Player, otherPlayers map[int32]*types.Player, quitChan <-chan struct{}) {
reader := bufio.NewReader(conn)
defer func() {
if r := recover(); r != nil {
log.Printf("Recovered from panic in HandleServerCommunication: %v", r)
}
conn.Close()
if player.QuitDone != nil {
close(player.QuitDone)
}
}()
actionTicker := time.NewTicker(types.ClientTickRate)
defer actionTicker.Stop()
defer conn.Close()
defer close(player.QuitDone)
// Create a channel to signal when goroutines are done
// Create error channel for goroutine communication
errChan := make(chan error, 1)
done := make(chan struct{})
// Create a set of current players to track disconnects
currentPlayers := make(map[int32]bool)
// Start message sending goroutine
go func() {
defer func() {
if r := recover(); r != nil {
log.Printf("Recovered from panic in message sender: %v", r)
errChan <- fmt.Errorf("message sender panic: %v", r)
}
}()
for {
select {
case <-quitChan:
@ -118,23 +132,23 @@ func HandleServerCommunication(conn net.Conn, playerID int32, player *types.Play
writeMessage(conn, disconnectMsg)
done <- struct{}{}
return
case <-done:
return
case <-actionTicker.C:
player.Lock()
if len(player.ActionQueue) > 0 {
actions := make([]*pb.Action, len(player.ActionQueue))
copy(actions, player.ActionQueue)
batch := &pb.ActionBatch{
PlayerId: playerID,
Actions: actions,
Tick: player.CurrentTick,
}
player.ActionQueue = player.ActionQueue[:0]
player.Unlock()
if err := writeMessage(conn, batch); err != nil {
log.Printf("Failed to send actions to server: %v", err)
errChan <- err
return
}
} else {
@ -144,93 +158,106 @@ func HandleServerCommunication(conn net.Conn, playerID int32, player *types.Play
}
}()
for {
select {
case <-quitChan:
done := make(chan struct{})
go func() {
<-done
close(player.QuitDone)
}()
// Main message receiving loop
go func() {
defer func() {
if r := recover(); r != nil {
log.Printf("Recovered from panic in message receiver: %v", r)
errChan <- fmt.Errorf("message receiver panic: %v", r)
}
}()
for {
select {
case <-done:
time.Sleep(100 * time.Millisecond)
case <-time.After(1 * time.Second):
log.Println("Shutdown timed out")
}
return
default:
// Read message length (4 bytes)
lengthBuf := make([]byte, 4)
if _, err := io.ReadFull(reader, lengthBuf); err != nil {
log.Printf("Failed to read message length: %v", err)
case <-quitChan:
return
}
messageLength := binary.BigEndian.Uint32(lengthBuf)
// Read the full message
messageBuf := make([]byte, messageLength)
if _, err := io.ReadFull(reader, messageBuf); err != nil {
log.Printf("Failed to read message body: %v", err)
return
}
var serverMessage pb.ServerMessage
if err := proto.Unmarshal(messageBuf, &serverMessage); err != nil {
log.Printf("Failed to unmarshal server message: %v", err)
continue
}
player.Lock()
player.CurrentTick = serverMessage.CurrentTick
tickDiff := serverMessage.CurrentTick - player.CurrentTick
if tickDiff > types.MaxTickDesync {
for _, state := range serverMessage.Players {
if state.PlayerId == playerID {
player.ForceResync(state)
break
default:
lengthBuf := make([]byte, 4)
if _, err := io.ReadFull(reader, lengthBuf); err != nil {
if err != io.EOF {
errChan <- fmt.Errorf("failed to read message length: %v", err)
}
return
}
}
player.Unlock()
messageLength := binary.BigEndian.Uint32(lengthBuf)
for _, state := range serverMessage.Players {
currentPlayers[state.PlayerId] = true
if state.PlayerId == playerID {
player.Lock()
// Update initial position if not set
if player.PosActual.X == 0 && player.PosActual.Z == 0 {
player.PosActual = rl.Vector3{
X: float32(state.X * types.TileSize),
Y: 0,
Z: float32(state.Y * types.TileSize),
}
player.PosTile = types.Tile{X: int(state.X), Y: int(state.Y)}
}
player.Unlock()
messageBuf := make([]byte, messageLength)
if _, err := io.ReadFull(reader, messageBuf); err != nil {
log.Printf("Failed to read message body: %v", err)
return
}
var serverMessage pb.ServerMessage
if err := proto.Unmarshal(messageBuf, &serverMessage); err != nil {
log.Printf("Failed to unmarshal server message: %v", err)
continue
}
if otherPlayer, exists := otherPlayers[state.PlayerId]; exists {
otherPlayer.UpdatePosition(state, types.ServerTickRate)
} else {
otherPlayers[state.PlayerId] = types.NewPlayer(state)
}
}
player.Lock()
player.CurrentTick = serverMessage.CurrentTick
// Remove players that are no longer in the server state
for id := range otherPlayers {
if !currentPlayers[id] {
delete(otherPlayers, id)
tickDiff := serverMessage.CurrentTick - player.CurrentTick
if tickDiff > types.MaxTickDesync {
for _, state := range serverMessage.Players {
if state.PlayerId == playerID {
player.ForceResync(state)
break
}
}
}
}
player.Unlock()
if handler, ok := player.UserData.(types.ChatMessageHandler); ok && len(serverMessage.ChatMessages) > 0 {
handler.HandleServerMessages(serverMessage.ChatMessages)
for _, state := range serverMessage.Players {
if state.PlayerId == playerID {
player.Lock()
// Update initial position if not set
if player.PosActual.X == 0 && player.PosActual.Z == 0 {
player.PosActual = rl.Vector3{
X: float32(state.X * types.TileSize),
Y: 0,
Z: float32(state.Y * types.TileSize),
}
player.PosTile = types.Tile{X: int(state.X), Y: int(state.Y)}
}
player.Unlock()
continue
}
if otherPlayer, exists := otherPlayers[state.PlayerId]; exists {
otherPlayer.UpdatePosition(state, types.ServerTickRate)
} else {
otherPlayers[state.PlayerId] = types.NewPlayer(state)
}
}
// Remove players that are no longer in the server state
for id := range otherPlayers {
if id != playerID {
delete(otherPlayers, id)
}
}
if handler, ok := player.UserData.(types.ChatMessageHandler); ok && len(serverMessage.ChatMessages) > 0 {
handler.HandleServerMessages(serverMessage.ChatMessages)
}
}
}
}()
// Wait for error or quit signal
select {
case <-quitChan:
// Send disconnect message
disconnectMsg := &pb.ActionBatch{
PlayerId: playerID,
Actions: []*pb.Action{{
Type: pb.Action_DISCONNECT,
PlayerId: playerID,
}},
}
writeMessage(conn, disconnectMsg)
case err := <-errChan:
log.Printf("Network error: %v", err)
}
}
@ -252,3 +279,50 @@ func writeMessage(conn net.Conn, msg proto.Message) error {
_, err = conn.Write(data)
return err
}
type Connection struct {
conn net.Conn
playerID int32
quitChan chan struct{}
quitDone chan struct{}
closeOnce sync.Once
}
func NewConnection(username, password string, isRegistering bool) (*Connection, error) {
conn, playerID, err := ConnectToServer(username, password, isRegistering)
if err != nil {
return nil, err
}
return &Connection{
conn: conn,
playerID: playerID,
quitChan: make(chan struct{}),
quitDone: make(chan struct{}),
}, nil
}
func (c *Connection) Close() {
c.closeOnce.Do(func() {
close(c.quitChan)
// Wait with timeout for network cleanup
select {
case <-c.quitDone:
// Clean shutdown completed
case <-time.After(500 * time.Millisecond):
log.Println("Network cleanup timed out")
}
c.conn.Close()
})
}
func (c *Connection) PlayerID() int32 {
return c.playerID
}
func (c *Connection) Start(player *types.Player, otherPlayers map[int32]*types.Player) {
go HandleServerCommunication(c.conn, c.playerID, player, otherPlayers, c.quitChan)
}
func (c *Connection) QuitChan() <-chan struct{} {
return c.quitChan
}