project-ely/internal/game/entity/command/handler.go

171 lines
3.5 KiB
Go

package command
import (
"context"
"log"
"sync"
"time"
"git.wisellama.rocks/Project-Ely/project-ely/internal/channels"
)
type Entity interface {
Draw() error
Update() error
MoveX(x float32)
MoveY(y float32)
SetSpeed(s float32)
}
type CommandHandler struct {
ctx context.Context
timeout time.Duration
entity Entity
commandChan chan Command
drawRequestChan chan struct{}
drawResponseChan chan struct{}
updateRequestChan chan struct{}
updateResponseChan chan struct{}
}
func NewCommandHandler(ctx context.Context, entity Entity) *CommandHandler {
commandChan := make(chan Command, 10)
drawRequestChan := make(chan struct{})
drawResponseChan := make(chan struct{})
updateRequestChan := make(chan struct{})
updateResponseChan := make(chan struct{})
defaultTimeout := time.Second
return &CommandHandler{
ctx: ctx,
timeout: defaultTimeout,
entity: entity,
commandChan: commandChan,
drawRequestChan: drawRequestChan,
drawResponseChan: drawResponseChan,
updateRequestChan: updateRequestChan,
updateResponseChan: updateResponseChan,
}
}
func (c *CommandHandler) closeResponses() {
close(c.drawResponseChan)
close(c.updateResponseChan)
}
func (c *CommandHandler) CloseRequests() {
close(c.drawRequestChan)
close(c.updateRequestChan)
close(c.commandChan)
}
func (c *CommandHandler) CommandRequest() chan Command {
return c.commandChan
}
func (c *CommandHandler) DrawRequest() chan struct{} {
return c.drawRequestChan
}
func (c *CommandHandler) DrawResponse() chan struct{} {
return c.drawResponseChan
}
func (c *CommandHandler) UpdateRequest() chan struct{} {
return c.updateRequestChan
}
func (c *CommandHandler) UpdateResponse() chan struct{} {
return c.updateResponseChan
}
func (c *CommandHandler) Run() {
defer c.closeResponses()
wg := sync.WaitGroup{}
running := true
for running {
select {
case <-c.drawRequestChan:
wg.Add(1)
go func() {
defer wg.Done()
c.DrawWithTimeout()
c.drawResponseChan <- struct{}{}
}()
case <-c.updateRequestChan:
wg.Add(1)
go func() {
defer wg.Done()
c.UpdateWithTimeout()
c.updateResponseChan <- struct{}{}
}()
case cmd := <-c.commandChan:
wg.Add(1)
go func(cmd Command) {
defer wg.Done()
c.HandleWithTimeout(cmd)
}(cmd)
case <-c.ctx.Done():
// Graceful shutdown
running = false
}
}
wg.Wait()
// Finish up anything in the queues
for cmd := range c.commandChan {
c.HandleWithTimeout(cmd)
}
for range c.drawRequestChan {
c.UpdateWithTimeout()
}
for range c.drawRequestChan {
c.DrawWithTimeout()
}
}
func (c *CommandHandler) HandleWithTimeout(cmd Command) {
err := channels.RunWithTimeout(c.timeout, func() error {
return c.Handle(cmd)
})
if err != nil {
log.Printf("handle with timeout error: %v\n", err)
}
}
func (c *CommandHandler) Handle(cmd Command) error {
switch cmd.key {
case MOVE_X:
c.entity.MoveX(cmd.value)
case MOVE_Y:
c.entity.MoveY(cmd.value)
case SET_SPEED:
c.entity.SetSpeed(cmd.value)
default:
log.Printf("unknown entity command: %v", cmd.key)
}
return nil
}
func (c *CommandHandler) DrawWithTimeout() {
err := channels.RunWithTimeout(c.timeout, func() error {
return c.entity.Draw()
})
if err != nil {
log.Printf("draw with timeout error: %v\n", err)
}
}
func (c *CommandHandler) UpdateWithTimeout() {
err := channels.RunWithTimeout(c.timeout, func() error {
return c.entity.Update()
})
if err != nil {
log.Printf("update with timeout error: %v\n", err)
}
}