From efefa4ced5139fd3054d8335d1e494738b522f1a Mon Sep 17 00:00:00 2001 From: Marco Date: Sun, 26 Nov 2023 21:29:14 +0100 Subject: [PATCH] Change websocket handling With this commit, we stop waiting for the websocket connection to be established before the game starts. Now, the Connection type is responsible for waiting for the websocket connection before writing. --- chess/game.go | 50 +++++++++++++--------------------------- chess/player.go | 45 ++++++++++++++---------------------- connection/type.go | 57 +++++++++++++++++++++++++++++++++------------- main.go | 10 +++++--- 4 files changed, 81 insertions(+), 81 deletions(-) diff --git a/chess/game.go b/chess/game.go index d2bdb79..df94680 100644 --- a/chess/game.go +++ b/chess/game.go @@ -2,9 +2,9 @@ package chess import ( "log" + "math/rand" "mchess_server/api" "mchess_server/types" - "time" "github.com/google/uuid" ) @@ -29,7 +29,7 @@ func NewGame() *Game { var game = Game{ id: uuid.New(), board: newBoard(), - gameState: PlayerToMove, + gameState: Init, } game.board.Init() @@ -44,15 +44,18 @@ func (game Game) GetPlayer1() *Player { return game.players[0] } +func (game Game) GetRandomPlayer() *Player { + index := rand.Int() % 2 + return game.players[index] +} + func (game Game) GetPlayer2() *Player { return game.players[1] } func (game *Game) prepare() { - ok := game.waitForWebsocketConnections() - if !ok { - return - } + game.players[0].color = types.White + game.players[1].color = types.Black err := game.notifyPlayersAboutGameStart() if err != nil { @@ -60,17 +63,11 @@ func (game *Game) prepare() { } } -//CHANGES: -/* -Do not wait for the players websocket connection before the game. -Let Connection do it. -Then here in game handler, just read from Connection (aka messagebuffer) and when data arrives, we read it -Question: how do we handle connection losses, should game handler observe state of connection and send a notification to the other player when one player is disconnected -Question: How would reconnect work in this scenario? -*/ func (game *Game) Handle() { defer game.killGame() + game.prepare() + var receivedMove types.Move var err error @@ -78,10 +75,14 @@ func (game *Game) Handle() { switch game.gameState { case Init: game.currentTurnPlayer = game.GetPlayer1() + game.gameState = Prepare + case Prepare: game.prepare() + game.gameState = PlayerToMove + case PlayerToMove: - log.Println("with player ", game.currentTurnPlayer, " to move") + log.Println("with ", game.currentTurnPlayer.GetPlayerColor(), " to move") receivedMove, err = game.currentTurnPlayer.ReadMove() if err != nil { log.Println("Error while reading message:", err) @@ -131,25 +132,6 @@ func (game *Game) killGame() { log.Println("Game should be killed") } -func (game *Game) waitForWebsocketConnections() bool { - timer := time.NewTimer(5 * time.Second) - numberOfConnections := 0 - waitingForPlayers := make(chan bool) - - go game.GetPlayer1().WaitForWebsocketConnection(waitingForPlayers) - go game.GetPlayer2().WaitForWebsocketConnection(waitingForPlayers) - - for numberOfConnections < 2 { - select { - case <-waitingForPlayers: - numberOfConnections++ - case <-timer.C: - return false - } - } - return true -} - func (game Game) notifyPlayersAboutGameStart() error { colorDeterminedPlayer1, err := api.GetColorDeterminedMessage(types.White) if err != nil { diff --git a/chess/player.go b/chess/player.go index 2ebc183..353da54 100644 --- a/chess/player.go +++ b/chess/player.go @@ -8,34 +8,32 @@ import ( "mchess_server/api" conn "mchess_server/connection" "mchess_server/types" - "time" "github.com/google/uuid" "nhooyr.io/websocket" ) type Player struct { - Uuid uuid.UUID - Conn *conn.Connection - InGame bool - wsConnEstablished chan bool - context context.Context + Uuid uuid.UUID + Conn *conn.Connection + InGame bool + color types.ChessColor } func NewPlayer(uuid uuid.UUID) *Player { return &Player{ - Uuid: uuid, - Conn: nil, - InGame: false, - wsConnEstablished: make(chan bool), - context: context.Background(), + Uuid: uuid, + Conn: conn.NewConnection(conn.WithContext(context.Background())), + InGame: false, } } -func (p *Player) SetConnection(ctx context.Context, ws *websocket.Conn) { - p.Conn = conn.NewConnection(conn.WithWebsocket(ws), conn.WithContext(p.context)) - p.context = ctx - p.wsConnEstablished <- true +func (p Player) HasWebsocketConnection() bool { + return p.Conn.HasWebsocketConnection() +} + +func (p *Player) SetWebsocketConnection(ctx context.Context, ws *websocket.Conn) { + p.Conn.SetWebsocketConnection(ws) } func (p *Player) SendMoveAndPosition(move types.Move, boardPosition string) error { @@ -58,9 +56,7 @@ func (p *Player) SendMoveAndPosition(move types.Move, boardPosition string) erro } func (p *Player) writeMessage(msg []byte) error { - log.Printf("Writing message: %s to player %s", string(msg), p.Uuid.String()) - - return p.Conn.Write(p.context, msg) + return p.Conn.Write(msg) } func (p *Player) ReadMove() (types.Move, error) { @@ -83,19 +79,12 @@ func (p *Player) ReadMove() (types.Move, error) { } func (p *Player) readMessage() ([]byte, error) { - msg, err := p.Conn.Read(p.context) + msg, err := p.Conn.Read() log.Printf("Reading message: %s from player %s", string(msg), p.Uuid.String()) return msg, err } -func (p *Player) WaitForWebsocketConnection(c chan bool) { - timer := time.NewTimer(5 * time.Second) - - select { - case <-p.wsConnEstablished: - c <- true - case <-timer.C: - return - } +func (p Player) GetPlayerColor() string { + return string(p.color) } diff --git a/connection/type.go b/connection/type.go index 5b2d31c..b61ae3a 100644 --- a/connection/type.go +++ b/connection/type.go @@ -2,34 +2,28 @@ package connection import ( "context" + "log" "nhooyr.io/websocket" ) type Connection struct { - ws *websocket.Conn - ctx context.Context - buffer MessageBuffer + ws *websocket.Conn + wsConnectionEstablished chan bool + ctx context.Context + buffer MessageBuffer } func NewConnection(options ...func(*Connection)) *Connection { connection := Connection{ - buffer: *newMessageBuffer(100), + buffer: *newMessageBuffer(100), + wsConnectionEstablished: make(chan bool), } for _, option := range options { option(&connection) } - if connection.ws != nil { - go func() { - for { - _, msg, _ := connection.ws.Read(connection.ctx) - connection.buffer.Insert(string(msg)) - } - }() - } - return &connection } @@ -45,13 +39,43 @@ func WithContext(ctx context.Context) func(*Connection) { } } -func (conn *Connection) Write(ctx context.Context, msg []byte) error { - return conn.ws.Write(ctx, websocket.MessageText, msg) +func (conn *Connection) HasWebsocketConnection() bool { + return conn.ws != nil } -func (conn *Connection) Read(ctx context.Context) ([]byte, error) { +func (conn *Connection) SetWebsocketConnection(ws *websocket.Conn) { + if ws == nil { + return + } + + conn.ws = ws + + select { + case conn.wsConnectionEstablished <- true: + default: + } + + go func() { + for { + _, msg, _ := conn.ws.Read(conn.ctx) + conn.buffer.Insert(string(msg)) + } + }() +} + +func (conn *Connection) Write(msg []byte) error { + if conn.ws == nil { //if ws is not yet set, we wait for it + <-conn.wsConnectionEstablished + } + + log.Printf("Writing message: %s", string(msg)) + return conn.ws.Write(conn.ctx, websocket.MessageText, msg) +} + +func (conn *Connection) Read() ([]byte, error) { msg, err := conn.buffer.Get() if err != nil { + conn.ws = nil return nil, err // Tell game-handler that connection was lost } @@ -60,4 +84,5 @@ func (conn *Connection) Read(ctx context.Context) ([]byte, error) { func (conn *Connection) Close(msg string) { conn.ws.Close(websocket.StatusCode(400), msg) + conn.ws = nil } diff --git a/main.go b/main.go index 9ed2096..fbc24f0 100644 --- a/main.go +++ b/main.go @@ -137,7 +137,7 @@ func waitForAndHandlePlayerID(ctx context.Context, conn *websocket.Conn) { return } - log.Println("read from websocket: ", msgType, string(msg), err) + log.Println("read from websocket endpoint: ", msgType, string(msg), err) var info api.PlayerInfo err = json.Unmarshal(msg, &info) @@ -149,14 +149,18 @@ func waitForAndHandlePlayerID(ctx context.Context, conn *websocket.Conn) { } lobby := lobbies.GetLobbyRegistry().GetLobbyByUUID(*info.LobbyID) + if lobby == nil { + conn.Close(websocket.StatusCode(400), "lobby not found") + } + player, found := lobby.GetPlayerByUUID(*info.PlayerID) if !found { conn.Close(websocket.StatusCode(400), "player not found") return } - if player.Conn != nil { + if player.Conn.HasWebsocketConnection() { player.Conn.Close("closing existing connection") } - player.SetConnection(ctx, conn) + player.SetWebsocketConnection(ctx, conn) log.Println("player after setting connection: ", player) }