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) } }