LastMUD/internal/server/connection.go

142 lines
2.4 KiB
Go

package server
import (
"bufio"
"context"
"net"
"sync"
"time"
"code.haedhutner.dev/mvv/LastMUD/internal/logging"
"github.com/google/uuid"
)
const MaxLastSeenTime = 90 * time.Second
const CheckAlivePeriod = 50 * time.Millisecond
const DeleteBeforeAndMoveToStartOfLine = "\033[1K\r"
type Connection struct {
ctx context.Context
wg *sync.WaitGroup
server *Server
identity uuid.UUID
conn *net.TCPConn
lastSeen time.Time
closeChan chan struct{}
}
func CreateConnection(server *Server, conn *net.TCPConn, ctx context.Context, wg *sync.WaitGroup) (c *Connection) {
logging.Info("Connect: ", conn.RemoteAddr())
conn.SetKeepAlive(true)
conn.SetKeepAlivePeriod(1 * time.Second)
c = &Connection{
ctx: ctx,
wg: wg,
server: server,
identity: uuid.New(),
conn: conn,
lastSeen: time.Now(),
closeChan: make(chan struct{}, 1),
}
c.wg.Add(2)
go c.listen()
go c.checkAlive()
server.game().ConnectPlayer(c.Id())
return
}
func (c *Connection) Id() uuid.UUID {
return c.identity
}
func (c *Connection) Write(output []byte) (err error) {
output = append([]byte(DeleteBeforeAndMoveToStartOfLine+"< "), output...)
output = append(output, []byte("\n> ")...)
_, err = c.conn.Write(output)
return
}
func (c *Connection) listen() {
defer c.wg.Done()
logging.Info("Listening on connection ", c.conn.RemoteAddr())
for {
c.conn.SetReadDeadline(time.Time{})
message, err := bufio.NewReader(c.conn).ReadString('\n')
if err != nil {
logging.Warn(err)
break
}
c.server.game().SendPlayerCommand(c.Id(), message)
c.lastSeen = time.Now()
}
}
func (c *Connection) checkAlive() {
defer c.wg.Done()
defer c.closeConnection()
for {
if c.shouldClose() {
c.Write([]byte("Server shutting down, bye bye!\r\n"))
break
}
if time.Since(c.lastSeen) > MaxLastSeenTime {
c.Write([]byte("You have been away for too long, bye bye!\r\n"))
break
}
_, err := c.conn.Write([]byte{0x00})
if err != nil {
break
}
time.Sleep(CheckAlivePeriod)
}
}
func (c *Connection) shouldClose() bool {
select {
case <-c.ctx.Done():
return true
default:
}
select {
case <-c.closeChan:
return true
default:
}
return false
}
func (c *Connection) CommandClose() {
c.closeChan <- struct{}{}
}
func (c *Connection) closeConnection() {
c.conn.Close()
c.server.game().DisconnectPlayer(c.Id())
logging.Info("Disconnected: ", c.conn.RemoteAddr())
}