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.
This commit is contained in:
Marco 2023-11-26 21:29:14 +01:00
parent 26242424ed
commit efefa4ced5
4 changed files with 81 additions and 81 deletions

View File

@ -2,9 +2,9 @@ package chess
import ( import (
"log" "log"
"math/rand"
"mchess_server/api" "mchess_server/api"
"mchess_server/types" "mchess_server/types"
"time"
"github.com/google/uuid" "github.com/google/uuid"
) )
@ -29,7 +29,7 @@ func NewGame() *Game {
var game = Game{ var game = Game{
id: uuid.New(), id: uuid.New(),
board: newBoard(), board: newBoard(),
gameState: PlayerToMove, gameState: Init,
} }
game.board.Init() game.board.Init()
@ -44,15 +44,18 @@ func (game Game) GetPlayer1() *Player {
return game.players[0] return game.players[0]
} }
func (game Game) GetRandomPlayer() *Player {
index := rand.Int() % 2
return game.players[index]
}
func (game Game) GetPlayer2() *Player { func (game Game) GetPlayer2() *Player {
return game.players[1] return game.players[1]
} }
func (game *Game) prepare() { func (game *Game) prepare() {
ok := game.waitForWebsocketConnections() game.players[0].color = types.White
if !ok { game.players[1].color = types.Black
return
}
err := game.notifyPlayersAboutGameStart() err := game.notifyPlayersAboutGameStart()
if err != nil { 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() { func (game *Game) Handle() {
defer game.killGame() defer game.killGame()
game.prepare()
var receivedMove types.Move var receivedMove types.Move
var err error var err error
@ -78,10 +75,14 @@ func (game *Game) Handle() {
switch game.gameState { switch game.gameState {
case Init: case Init:
game.currentTurnPlayer = game.GetPlayer1() game.currentTurnPlayer = game.GetPlayer1()
game.gameState = Prepare
case Prepare: case Prepare:
game.prepare() game.prepare()
game.gameState = PlayerToMove
case PlayerToMove: case PlayerToMove:
log.Println("with player ", game.currentTurnPlayer, " to move") log.Println("with ", game.currentTurnPlayer.GetPlayerColor(), " to move")
receivedMove, err = game.currentTurnPlayer.ReadMove() receivedMove, err = game.currentTurnPlayer.ReadMove()
if err != nil { if err != nil {
log.Println("Error while reading message:", err) log.Println("Error while reading message:", err)
@ -131,25 +132,6 @@ func (game *Game) killGame() {
log.Println("Game should be killed") 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 { func (game Game) notifyPlayersAboutGameStart() error {
colorDeterminedPlayer1, err := api.GetColorDeterminedMessage(types.White) colorDeterminedPlayer1, err := api.GetColorDeterminedMessage(types.White)
if err != nil { if err != nil {

View File

@ -8,34 +8,32 @@ import (
"mchess_server/api" "mchess_server/api"
conn "mchess_server/connection" conn "mchess_server/connection"
"mchess_server/types" "mchess_server/types"
"time"
"github.com/google/uuid" "github.com/google/uuid"
"nhooyr.io/websocket" "nhooyr.io/websocket"
) )
type Player struct { type Player struct {
Uuid uuid.UUID Uuid uuid.UUID
Conn *conn.Connection Conn *conn.Connection
InGame bool InGame bool
wsConnEstablished chan bool color types.ChessColor
context context.Context
} }
func NewPlayer(uuid uuid.UUID) *Player { func NewPlayer(uuid uuid.UUID) *Player {
return &Player{ return &Player{
Uuid: uuid, Uuid: uuid,
Conn: nil, Conn: conn.NewConnection(conn.WithContext(context.Background())),
InGame: false, InGame: false,
wsConnEstablished: make(chan bool),
context: context.Background(),
} }
} }
func (p *Player) SetConnection(ctx context.Context, ws *websocket.Conn) { func (p Player) HasWebsocketConnection() bool {
p.Conn = conn.NewConnection(conn.WithWebsocket(ws), conn.WithContext(p.context)) return p.Conn.HasWebsocketConnection()
p.context = ctx }
p.wsConnEstablished <- true
func (p *Player) SetWebsocketConnection(ctx context.Context, ws *websocket.Conn) {
p.Conn.SetWebsocketConnection(ws)
} }
func (p *Player) SendMoveAndPosition(move types.Move, boardPosition string) error { 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 { func (p *Player) writeMessage(msg []byte) error {
log.Printf("Writing message: %s to player %s", string(msg), p.Uuid.String()) return p.Conn.Write(msg)
return p.Conn.Write(p.context, msg)
} }
func (p *Player) ReadMove() (types.Move, error) { func (p *Player) ReadMove() (types.Move, error) {
@ -83,19 +79,12 @@ func (p *Player) ReadMove() (types.Move, error) {
} }
func (p *Player) readMessage() ([]byte, 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()) log.Printf("Reading message: %s from player %s", string(msg), p.Uuid.String())
return msg, err return msg, err
} }
func (p *Player) WaitForWebsocketConnection(c chan bool) { func (p Player) GetPlayerColor() string {
timer := time.NewTimer(5 * time.Second) return string(p.color)
select {
case <-p.wsConnEstablished:
c <- true
case <-timer.C:
return
}
} }

View File

@ -2,34 +2,28 @@ package connection
import ( import (
"context" "context"
"log"
"nhooyr.io/websocket" "nhooyr.io/websocket"
) )
type Connection struct { type Connection struct {
ws *websocket.Conn ws *websocket.Conn
ctx context.Context wsConnectionEstablished chan bool
buffer MessageBuffer ctx context.Context
buffer MessageBuffer
} }
func NewConnection(options ...func(*Connection)) *Connection { func NewConnection(options ...func(*Connection)) *Connection {
connection := Connection{ connection := Connection{
buffer: *newMessageBuffer(100), buffer: *newMessageBuffer(100),
wsConnectionEstablished: make(chan bool),
} }
for _, option := range options { for _, option := range options {
option(&connection) option(&connection)
} }
if connection.ws != nil {
go func() {
for {
_, msg, _ := connection.ws.Read(connection.ctx)
connection.buffer.Insert(string(msg))
}
}()
}
return &connection return &connection
} }
@ -45,13 +39,43 @@ func WithContext(ctx context.Context) func(*Connection) {
} }
} }
func (conn *Connection) Write(ctx context.Context, msg []byte) error { func (conn *Connection) HasWebsocketConnection() bool {
return conn.ws.Write(ctx, websocket.MessageText, msg) 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() msg, err := conn.buffer.Get()
if err != nil { if err != nil {
conn.ws = nil
return nil, err // Tell game-handler that connection was lost 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) { func (conn *Connection) Close(msg string) {
conn.ws.Close(websocket.StatusCode(400), msg) conn.ws.Close(websocket.StatusCode(400), msg)
conn.ws = nil
} }

10
main.go
View File

@ -137,7 +137,7 @@ func waitForAndHandlePlayerID(ctx context.Context, conn *websocket.Conn) {
return return
} }
log.Println("read from websocket: ", msgType, string(msg), err) log.Println("read from websocket endpoint: ", msgType, string(msg), err)
var info api.PlayerInfo var info api.PlayerInfo
err = json.Unmarshal(msg, &info) err = json.Unmarshal(msg, &info)
@ -149,14 +149,18 @@ func waitForAndHandlePlayerID(ctx context.Context, conn *websocket.Conn) {
} }
lobby := lobbies.GetLobbyRegistry().GetLobbyByUUID(*info.LobbyID) lobby := lobbies.GetLobbyRegistry().GetLobbyByUUID(*info.LobbyID)
if lobby == nil {
conn.Close(websocket.StatusCode(400), "lobby not found")
}
player, found := lobby.GetPlayerByUUID(*info.PlayerID) player, found := lobby.GetPlayerByUUID(*info.PlayerID)
if !found { if !found {
conn.Close(websocket.StatusCode(400), "player not found") conn.Close(websocket.StatusCode(400), "player not found")
return return
} }
if player.Conn != nil { if player.Conn.HasWebsocketConnection() {
player.Conn.Close("closing existing connection") player.Conn.Close("closing existing connection")
} }
player.SetConnection(ctx, conn) player.SetWebsocketConnection(ctx, conn)
log.Println("player after setting connection: ", player) log.Println("player after setting connection: ", player)
} }