LastMUD/internal/game/game.go

231 lines
4.1 KiB
Go
Raw Normal View History

2025-06-20 16:26:39 +03:00
package game
import (
"code.haedhutner.dev/mvv/LastMUD/internal/game/logic/world"
2025-06-20 16:26:39 +03:00
"context"
"sync"
"time"
"code.haedhutner.dev/mvv/LastMUD/internal/ecs"
"code.haedhutner.dev/mvv/LastMUD/internal/game/data"
"code.haedhutner.dev/mvv/LastMUD/internal/game/logic"
2025-06-20 16:26:39 +03:00
"code.haedhutner.dev/mvv/LastMUD/internal/logging"
2025-06-22 17:54:07 +03:00
"github.com/google/uuid"
2025-06-20 16:26:39 +03:00
)
const TickRate = 50 * time.Millisecond
2025-06-20 16:26:39 +03:00
2025-06-29 18:21:22 +03:00
type InputType = string
const (
Connect InputType = "Connect"
Disconnect = "Disconnect"
Command = "Command"
)
type Input struct {
connId uuid.UUID
inputType InputType
command string
}
type Output struct {
connId uuid.UUID
contents []byte
closeConnection bool
2025-06-22 17:54:07 +03:00
}
func (g Output) Id() uuid.UUID {
2025-06-22 17:54:07 +03:00
return g.connId
}
func (g Output) Contents() []byte {
2025-06-22 17:54:07 +03:00
return g.contents
2025-06-20 16:26:39 +03:00
}
func (g Output) ShouldCloseConnection() bool {
return g.closeConnection
}
type Game struct {
2025-06-20 16:26:39 +03:00
ctx context.Context
wg *sync.WaitGroup
2025-06-22 17:54:07 +03:00
world *World
2025-06-22 17:54:07 +03:00
2025-06-29 18:21:22 +03:00
input chan Input
output chan Output
2025-06-29 18:21:22 +03:00
stop context.CancelFunc
2025-06-20 16:26:39 +03:00
}
func CreateGame(ctx context.Context, wg *sync.WaitGroup) (game *Game) {
2025-06-29 18:21:22 +03:00
ctx, cancel := context.WithCancel(ctx)
game = &Game{
wg: wg,
ctx: ctx,
2025-06-29 18:21:22 +03:00
input: make(chan Input, 1000),
output: make(chan Output, 1000),
world: CreateGameWorld(),
2025-06-29 18:21:22 +03:00
stop: cancel,
2025-06-20 16:26:39 +03:00
}
ecs.RegisterSystems(game.world.World, logic.CreateSystems()...)
2025-06-22 22:27:56 +03:00
2025-06-20 16:26:39 +03:00
wg.Add(1)
go game.start()
return
}
// ConsumeNextOutput will block if no output present
func (game *Game) ConsumeNextOutput() *Output {
2025-06-29 18:21:22 +03:00
if game.shouldStop() {
return nil
}
2025-06-24 16:37:26 +03:00
select {
case output := <-game.output:
return &output
default:
return nil
}
}
2025-06-29 18:21:22 +03:00
func (game *Game) Connect(connectionId uuid.UUID) {
if game.shouldStop() {
return
}
game.input <- Input{
inputType: Connect,
connId: connectionId,
}
}
2025-06-29 18:21:22 +03:00
func (game *Game) Disconnect(connectionId uuid.UUID) {
if game.shouldStop() {
return
}
game.input <- Input{
inputType: Disconnect,
connId: connectionId,
}
}
2025-06-29 18:21:22 +03:00
func (game *Game) SendCommand(connectionId uuid.UUID, cmd string) {
if game.shouldStop() {
return
}
game.input <- Input{
inputType: Command,
connId: connectionId,
command: cmd,
}
}
func (game *Game) start() {
2025-06-20 16:26:39 +03:00
defer game.wg.Done()
defer game.shutdown()
logging.Info("Starting LastMUD...")
lastTick := time.Now()
for {
now := time.Now()
if game.shouldStop() {
break
}
game.tick(now.Sub(lastTick))
// Tick at regular intervals
if time.Since(lastTick) < TickRate {
time.Sleep(TickRate - time.Since(lastTick))
}
lastTick = now
}
}
func (game *Game) consumeOutputs() {
2025-06-29 18:21:22 +03:00
entities := ecs.FindEntitiesWithComponents(game.world.World, data.TypeIsOutput, data.TypeConnectionId, data.TypeContents)
for _, entity := range entities {
output := Output{}
connId, _ := ecs.GetComponent[data.ConnectionIdComponent](game.world.World, entity)
output.connId = connId.ConnectionId
contents, hasContents := ecs.GetComponent[data.ContentsComponent](game.world.World, entity)
if hasContents {
output.contents = contents.Contents
}
_, shouldClose := ecs.GetComponent[data.CloseConnectionComponent](game.world.World, entity)
output.closeConnection = shouldClose
game.enqeueOutput(output)
}
2025-06-27 09:45:55 +03:00
ecs.DeleteEntities(game.world.World, entities...)
}
func (game *Game) shutdown() {
2025-06-20 16:26:39 +03:00
logging.Info("Stopping LastMUD...")
2025-06-22 17:54:07 +03:00
close(game.output)
2025-06-29 18:21:22 +03:00
close(game.input)
2025-06-20 16:26:39 +03:00
}
func (game *Game) shouldStop() bool {
2025-06-20 16:26:39 +03:00
select {
case <-game.ctx.Done():
return true
default:
return false
}
}
2025-06-29 18:21:22 +03:00
func (game *Game) nextInput() *Input {
select {
case input := <-game.input:
return &input
default:
return nil
}
}
func (game *Game) enqeueOutput(output Output) {
2025-06-22 17:54:07 +03:00
game.output <- output
}
func (game *Game) tick(delta time.Duration) {
2025-06-29 18:21:22 +03:00
for {
input := game.nextInput()
if input == nil {
break
}
switch input.inputType {
case Connect:
world.CreatePlayerConnectEvent(game.world.World, input.connId)
case Disconnect:
world.CreatePlayerDisconnectEvent(game.world.World, input.connId)
case Command:
world.CreateSubmitInputEvent(game.world.World, input.connId, input.command)
}
}
game.world.Tick(delta)
2025-06-29 18:21:22 +03:00
game.consumeOutputs()
2025-06-20 16:26:39 +03:00
}