Implement a command queue on the world

Not currently executed
This commit is contained in:
Marc Di Luzio 2020-06-06 14:44:59 +01:00
parent e3ce87e964
commit 0a0a32cf58
6 changed files with 120 additions and 15 deletions

View file

@ -12,3 +12,6 @@ type Command struct {
Bearing string `json:"bearing,omitempty"` Bearing string `json:"bearing,omitempty"`
Duration int `json:"duration,omitempty"` Duration int `json:"duration,omitempty"`
} }
// CommandStream is a list of commands to execute in order
type CommandStream []Command

View file

@ -24,7 +24,7 @@ func TestCommand_Move(t *testing.T) {
duration := 1 duration := 1
// Try the move command // Try the move command
moveCommand := Command{Command: CommandMove, Bearing: bearing.String(), Duration: duration} moveCommand := Command{Command: CommandMove, Bearing: bearing.String(), Duration: duration}
assert.NoError(t, world.Execute(a, moveCommand), "Failed to execute move command") assert.NoError(t, world.Enqueue(a, moveCommand), "Failed to execute move command")
newpos, err := world.RoverPosition(a) newpos, err := world.RoverPosition(a)
assert.NoError(t, err, "Failed to set position for rover") assert.NoError(t, err, "Failed to set position for rover")

View file

@ -2,6 +2,7 @@ package game
import ( import (
"fmt" "fmt"
"sync"
"github.com/google/uuid" "github.com/google/uuid"
) )
@ -10,6 +11,15 @@ import (
type World struct { type World struct {
// Rovers is a id->data map of all the rovers in the game // Rovers is a id->data map of all the rovers in the game
Rovers map[uuid.UUID]Rover `json:"rovers"` Rovers map[uuid.UUID]Rover `json:"rovers"`
// Mutex to lock around all world operations
worldMutex sync.RWMutex
// Commands is the set of currently executing command streams per rover
CommandQueue map[uuid.UUID]CommandStream `json:"commands"`
// Mutex to lock around command operations
cmdMutex sync.RWMutex
} }
// RoverAttributes contains attributes of a rover // RoverAttributes contains attributes of a rover
@ -36,12 +46,16 @@ type Rover struct {
// NewWorld creates a new world object // NewWorld creates a new world object
func NewWorld() *World { func NewWorld() *World {
return &World{ return &World{
Rovers: make(map[uuid.UUID]Rover), Rovers: make(map[uuid.UUID]Rover),
CommandQueue: make(map[uuid.UUID]CommandStream),
} }
} }
// SpawnRover adds an rover to the game // SpawnRover adds an rover to the game
func (w *World) SpawnRover() uuid.UUID { func (w *World) SpawnRover() uuid.UUID {
w.worldMutex.Lock()
defer w.worldMutex.Unlock()
// Initialise the rover // Initialise the rover
rover := Rover{ rover := Rover{
Id: uuid.New(), Id: uuid.New(),
@ -64,6 +78,9 @@ func (w *World) SpawnRover() uuid.UUID {
// Removes an rover from the game // Removes an rover from the game
func (w *World) DestroyRover(id uuid.UUID) error { func (w *World) DestroyRover(id uuid.UUID) error {
w.worldMutex.Lock()
defer w.worldMutex.Unlock()
if _, ok := w.Rovers[id]; ok { if _, ok := w.Rovers[id]; ok {
delete(w.Rovers, id) delete(w.Rovers, id)
} else { } else {
@ -73,7 +90,10 @@ func (w *World) DestroyRover(id uuid.UUID) error {
} }
// RoverAttributes returns the attributes of a requested rover // RoverAttributes returns the attributes of a requested rover
func (w World) RoverAttributes(id uuid.UUID) (RoverAttributes, error) { func (w *World) RoverAttributes(id uuid.UUID) (RoverAttributes, error) {
w.worldMutex.RLock()
defer w.worldMutex.RUnlock()
if i, ok := w.Rovers[id]; ok { if i, ok := w.Rovers[id]; ok {
return i.Attributes, nil return i.Attributes, nil
} else { } else {
@ -82,7 +102,10 @@ func (w World) RoverAttributes(id uuid.UUID) (RoverAttributes, error) {
} }
// RoverPosition returns the position of a given rover // RoverPosition returns the position of a given rover
func (w World) RoverPosition(id uuid.UUID) (Vector, error) { func (w *World) RoverPosition(id uuid.UUID) (Vector, error) {
w.worldMutex.RLock()
defer w.worldMutex.RUnlock()
if i, ok := w.Rovers[id]; ok { if i, ok := w.Rovers[id]; ok {
return i.Pos, nil return i.Pos, nil
} else { } else {
@ -92,6 +115,9 @@ func (w World) RoverPosition(id uuid.UUID) (Vector, error) {
// WarpRover sets an rovers position // WarpRover sets an rovers position
func (w *World) WarpRover(id uuid.UUID, pos Vector) error { func (w *World) WarpRover(id uuid.UUID, pos Vector) error {
w.worldMutex.Lock()
defer w.worldMutex.Unlock()
if i, ok := w.Rovers[id]; ok { if i, ok := w.Rovers[id]; ok {
i.Pos = pos i.Pos = pos
w.Rovers[id] = i w.Rovers[id] = i
@ -102,10 +128,13 @@ func (w *World) WarpRover(id uuid.UUID, pos Vector) error {
} }
// SetPosition sets an rovers position // SetPosition sets an rovers position
func (w *World) MoveRover(id uuid.UUID, bearing Direction, duration int) (Vector, error) { func (w *World) MoveRover(id uuid.UUID, bearing Direction) (Vector, error) {
w.worldMutex.Lock()
defer w.worldMutex.Unlock()
if i, ok := w.Rovers[id]; ok { if i, ok := w.Rovers[id]; ok {
// Calculate the distance // Calculate the distance
distance := i.Attributes.Speed * duration distance := i.Attributes.Speed
// Calculate the full movement based on the bearing // Calculate the full movement based on the bearing
move := bearing.Vector().Multiplied(distance) move := bearing.Vector().Multiplied(distance)
@ -128,7 +157,10 @@ type RadarDescription struct {
} }
// RadarFromRover can be used to query what a rover can currently see // RadarFromRover can be used to query what a rover can currently see
func (w World) RadarFromRover(id uuid.UUID) (RadarDescription, error) { func (w *World) RadarFromRover(id uuid.UUID) (RadarDescription, error) {
w.worldMutex.RLock()
defer w.worldMutex.RUnlock()
if r1, ok := w.Rovers[id]; ok { if r1, ok := w.Rovers[id]; ok {
var desc RadarDescription var desc RadarDescription
@ -145,19 +177,89 @@ func (w World) RadarFromRover(id uuid.UUID) (RadarDescription, error) {
} }
} }
// Execute will run the commands given // Enqueue will queue the commands given
func (w *World) Execute(id uuid.UUID, commands ...Command) error { func (w *World) Enqueue(rover uuid.UUID, commands ...Command) error {
// First validate the commands
for _, c := range commands { for _, c := range commands {
switch c.Command { switch c.Command {
case "move": case "move":
if dir, err := DirectionFromString(c.Bearing); err != nil { if _, err := DirectionFromString(c.Bearing); err != nil {
return fmt.Errorf("unknown direction: %s", c.Bearing) return fmt.Errorf("unknown direction: %s", c.Bearing)
} else if _, err := w.MoveRover(id, dir, c.Duration); err != nil {
return err
} }
default: default:
return fmt.Errorf("unknown command: %s", c.Command) return fmt.Errorf("unknown command: %s", c.Command)
} }
} }
// Lock our commands edit
w.cmdMutex.Lock()
defer w.cmdMutex.Unlock()
// Append the commands to the current set
cmds := w.CommandQueue[rover]
w.CommandQueue[rover] = append(cmds, commands...)
return nil return nil
} }
// Execute will execute any commands in the current command queue
func (w *World) Execute() error {
w.cmdMutex.Lock()
defer w.cmdMutex.Unlock()
// Iterate through all commands
for rover, cmds := range w.CommandQueue {
if len(cmds) != 0 {
// Extract the first command in the queue
c := cmds[0]
// Execute the command and clear up if requested
if done, err := w.ExecuteCommand(&c, rover); err != nil {
w.CommandQueue[rover] = cmds[1:]
fmt.Println(err)
} else if done {
w.CommandQueue[rover] = cmds[1:]
} else {
w.CommandQueue[rover][0] = c
}
// If there was an error
} else {
// Clean out the empty entry
delete(w.CommandQueue, rover)
}
}
return nil
}
// ExecuteCommand will execute a single command
func (w *World) ExecuteCommand(c *Command, rover uuid.UUID) (finished bool, err error) {
w.worldMutex.Lock()
defer w.worldMutex.Unlock()
switch c.Command {
case "move":
if dir, err := DirectionFromString(c.Bearing); err != nil {
return true, fmt.Errorf("unknown direction in command %+v, skipping: %s\n", c, err)
} else if _, err := w.MoveRover(rover, dir); err != nil {
return true, fmt.Errorf("error moving rover in command %+v, skipping: %s\n", c, err)
} else {
// If we've successfully moved, reduce the duration by 1
c.Duration -= 1
// If we've used up the full duration, remove it, otherwise update
if c.Duration == 0 {
finished = true
}
}
default:
return true, fmt.Errorf("unknown command: %s", c.Command)
}
return
}

View file

@ -73,7 +73,7 @@ func TestWorld_GetSetMovePosition(t *testing.T) {
bearing := North bearing := North
duration := 1 duration := 1
newpos, err = world.MoveRover(a, bearing, duration) newpos, err = world.MoveRover(a, bearing)
assert.NoError(t, err, "Failed to set position for rover") assert.NoError(t, err, "Failed to set position for rover")
pos.Add(Vector{0, attribs.Speed * duration}) // We should have move one unit of the speed north pos.Add(Vector{0, attribs.Speed * duration}) // We should have move one unit of the speed north
assert.Equal(t, pos, newpos, "Failed to correctly move position for rover") assert.Equal(t, pos, newpos, "Failed to correctly move position for rover")

View file

@ -146,7 +146,7 @@ func HandleCommand(s *Server, vars map[string]string, b io.ReadCloser, w io.Writ
} else if inst, err := s.accountant.GetRover(id); err != nil { } else if inst, err := s.accountant.GetRover(id); err != nil {
response.Error = fmt.Sprintf("Provided account has no rover: %s", err) response.Error = fmt.Sprintf("Provided account has no rover: %s", err)
} else if err := s.world.Execute(inst, data.Commands...); err != nil { } else if err := s.world.Enqueue(inst, data.Commands...); err != nil {
response.Error = fmt.Sprintf("Failed to execute commands: %s", err) response.Error = fmt.Sprintf("Failed to execute commands: %s", err)
} else { } else {

View file

@ -114,7 +114,7 @@ func (s *Server) Initialise() (err error) {
} }
// Addr will return the server address set after the listen // Addr will return the server address set after the listen
func (s Server) Addr() string { func (s *Server) Addr() string {
return s.address return s.address
} }