add a bunch of mutexes and shit but still have racist conditions and panics LOL
This commit is contained in:
@ -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
|
||||
}
|
||||
|
Reference in New Issue
Block a user