Files
gomoku/server/network/dispatch.go
T
tiennm99 9e14f3a9b3 feat(server): new ws network layer and protobuf dispatch on :1999/gomoku
- Add server/network/{server,codec,reader,writer,dispatch,handlers_stateless}.go
- Single /gomoku WS endpoint; binary protobuf frames only (no JSON/base64)
- Per-connection reader + writer goroutines; write mutex serialises WS writes
- Dispatch type-switch: stateless (heartbeat, set_nickname, get_rooms,
  set_client_info, client_exit) inline; stateful requests → player.CmdCh
- Add Player.SendCh, CmdCh, LastHeartbeat, ClientVersion, Send() to database.Player
- Rewire main.go: NewServer(":1999").Serve() + database.StartCleanup()
- Empty wss.go (old shim superseded by server.go)
2026-04-11 13:56:22 +07:00

68 lines
2.1 KiB
Go

package network
import (
"github.com/tiennm99/gomoku/server/database"
"github.com/tiennm99/gomoku/server/pkg/log"
"github.com/tiennm99/gomoku/server/protocol"
)
// Dispatch routes an incoming Request to the appropriate handler.
//
// Stateless requests (heartbeat, get_rooms, set_nickname, set_client_info,
// client_exit) are handled inline on the reader goroutine — they must return fast.
//
// Stateful requests (create_room, join_room, game_move, etc.) are pushed onto
// player.CmdCh for the state machine goroutine (phase-06) to consume in order.
//
// Overflow policy: if CmdCh is full, the request is logged and dropped rather
// than blocking the reader goroutine (prevents backpressure deadlocks).
func Dispatch(player *database.Player, req *protocol.Request) {
switch req.Payload.(type) {
// --- Stateless handlers (inline) ---
case *protocol.Request_Heartbeat:
handleHeartbeat(player, req)
case *protocol.Request_SetNickname:
handleSetNickname(player, req)
case *protocol.Request_GetRooms:
handleGetRooms(player, req)
case *protocol.Request_SetClientInfo:
handleSetClientInfo(player, req)
case *protocol.Request_ClientExit:
handleClientExit(player, req)
// --- Stateful handlers (pushed to cmdCh for state machine) ---
case *protocol.Request_CreateRoom,
*protocol.Request_CreatePveRoom,
*protocol.Request_JoinRoom,
*protocol.Request_GameStarting,
*protocol.Request_GameReady,
*protocol.Request_GameMove,
*protocol.Request_GameReset,
*protocol.Request_WatchGame,
*protocol.Request_WatchGameExit:
pushToCmdCh(player, req)
default:
log.Errorf("[dispatch] player %d: unhandled request type %T, dropping\n", player.ID, req.Payload)
}
}
// pushToCmdCh enqueues req on player.CmdCh without blocking.
// If the channel is full or nil, the request is dropped with a warning.
func pushToCmdCh(player *database.Player, req *protocol.Request) {
if player.CmdCh == nil {
log.Errorf("[dispatch] player %d: CmdCh is nil, dropping %T\n", player.ID, req.Payload)
return
}
select {
case player.CmdCh <- req:
default:
log.Errorf("[dispatch] player %d: CmdCh full (cap %d), dropping %T\n",
player.ID, cap(player.CmdCh), req.Payload)
}
}