WIP. Probably need to cherry pick stuff out of here.
Added ClientConnection and server-side handshake. Added MessageSerialiser and Deserialiser. #story[1095]
This commit is contained in:
@@ -0,0 +1,243 @@
|
||||
package mock.model;
|
||||
|
||||
|
||||
import mock.app.MockOutput;
|
||||
import mock.enums.ConnectionStateEnum;
|
||||
import shared.exceptions.HandshakeException;
|
||||
import mock.exceptions.SourceIDAllocationException;
|
||||
import mock.model.commandFactory.CompositeCommand;
|
||||
import network.Messages.*;
|
||||
import network.Messages.Enums.JoinAcceptanceEnum;
|
||||
import network.Messages.Enums.MessageType;
|
||||
import network.Messages.Enums.RequestToJoinEnum;
|
||||
import network.StreamRelated.MessageDeserialiser;
|
||||
import network.StreamRelated.MessageSerialiser;
|
||||
import visualiser.gameController.ControllerServer;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.Socket;
|
||||
import java.util.concurrent.BlockingQueue;
|
||||
import java.util.concurrent.LinkedBlockingQueue;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
/**
|
||||
* This class handles the client connection handshake, and creation of MockOutput and ControllerServer.
|
||||
*/
|
||||
public class ClientConnection implements Runnable {
|
||||
|
||||
/**
|
||||
* The socket for the client's connection.
|
||||
*/
|
||||
private Socket socket;
|
||||
|
||||
/**
|
||||
* Periodically sends HeartBeat messages to client.
|
||||
*/
|
||||
private HeartBeatService heartBeatService;
|
||||
|
||||
|
||||
/**
|
||||
* Used to allocate source ID to client, if they request to participate.
|
||||
*/
|
||||
private SourceIdAllocator sourceIdAllocator;
|
||||
|
||||
/**
|
||||
* Latest snapshot of the race, to send to client. Currently only used for XML messages.
|
||||
*/
|
||||
private LatestMessages latestMessages;
|
||||
|
||||
|
||||
/**
|
||||
* Collection of commands from client for race to execute.
|
||||
*/
|
||||
private CompositeCommand compositeCommand;
|
||||
|
||||
/**
|
||||
* Used to send the race snapshot to client.
|
||||
*/
|
||||
private MockOutput mockOutput;
|
||||
|
||||
/**
|
||||
* Used to receive client input, and turn it into commands.
|
||||
*/
|
||||
private ControllerServer controllerServer;
|
||||
|
||||
|
||||
/**
|
||||
* Used to write messages to socket.
|
||||
*/
|
||||
private MessageSerialiser messageSerialiser;
|
||||
|
||||
/**
|
||||
* Stores messages to write to socket.
|
||||
*/
|
||||
private BlockingQueue<AC35Data> outputQueue;
|
||||
|
||||
/**
|
||||
* Used to read messages from socket.
|
||||
*/
|
||||
private MessageDeserialiser messageDeserialiser;
|
||||
|
||||
/**
|
||||
* Stores messages read from socket.
|
||||
*/
|
||||
private BlockingQueue<AC35Data> inputQueue;
|
||||
|
||||
/**
|
||||
* The state of the connection to the client.
|
||||
*/
|
||||
private ConnectionStateEnum connectionState = ConnectionStateEnum.UNKNOWN;
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Creates a client connection, using a given socket.
|
||||
* @param socket The socket which connects to the client.
|
||||
* @param sourceIdAllocator Used to allocate a source ID for the client.
|
||||
* @param latestMessages Latest race snapshot to send to client.
|
||||
* @param compositeCommand Collection of commands for race to execute.
|
||||
* @throws IOException Thrown if there is a problem with the client socket.
|
||||
*/
|
||||
public ClientConnection(Socket socket, SourceIdAllocator sourceIdAllocator, LatestMessages latestMessages, CompositeCommand compositeCommand) throws IOException {
|
||||
this.socket = socket;
|
||||
this.sourceIdAllocator = sourceIdAllocator;
|
||||
this.latestMessages = latestMessages;
|
||||
this.compositeCommand = compositeCommand;
|
||||
|
||||
this.outputQueue = new LinkedBlockingQueue<>();
|
||||
this.inputQueue = new LinkedBlockingQueue<>();
|
||||
|
||||
|
||||
this.messageSerialiser = new MessageSerialiser(socket.getOutputStream(), outputQueue);
|
||||
this.messageDeserialiser = new MessageDeserialiser(socket.getInputStream(), inputQueue);
|
||||
|
||||
new Thread(messageSerialiser, "ClientConnection()->MessageSerialiser thread " + messageSerialiser).start();
|
||||
new Thread(messageDeserialiser, "ClientConnection()->MessageDeserialiser thread " + messageDeserialiser).start();
|
||||
|
||||
|
||||
this.heartBeatService = new HeartBeatService(outputQueue);
|
||||
new Thread(heartBeatService, "ClientConnection()->HeartBeatService thread " + heartBeatService).start();
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
handshake();
|
||||
|
||||
} catch (HandshakeException | SourceIDAllocationException e) {
|
||||
Logger.getGlobal().log(Level.WARNING, "Client handshake failed.", e);
|
||||
Thread.currentThread().interrupt();
|
||||
return;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Initiates the handshake with the client.
|
||||
* @throws HandshakeException Thrown if something goes wrong with the handshake.
|
||||
* @throws SourceIDAllocationException Thrown if we cannot allocate a sourceID.
|
||||
*/
|
||||
private void handshake() throws SourceIDAllocationException, HandshakeException {
|
||||
|
||||
//This function is a bit messy, and could probably be refactored a bit.
|
||||
|
||||
connectionState = ConnectionStateEnum.WAITING_FOR_HANDSHAKE;
|
||||
|
||||
|
||||
|
||||
RequestToJoin requestToJoin = waitForRequestToJoin();
|
||||
|
||||
int allocatedSourceID = 0;
|
||||
|
||||
//If they want to participate, give them a source ID number.
|
||||
if (requestToJoin.getRequestType() == RequestToJoinEnum.PARTICIPANT) {
|
||||
|
||||
allocatedSourceID = sourceIdAllocator.allocateSourceID();
|
||||
|
||||
this.controllerServer = new ControllerServer(compositeCommand, inputQueue, allocatedSourceID);
|
||||
new Thread(controllerServer, "ClientConnection.run()->ControllerServer thread" + controllerServer).start();
|
||||
|
||||
}
|
||||
|
||||
this.mockOutput = new MockOutput(latestMessages, outputQueue);
|
||||
new Thread(mockOutput, "ClientConnection.run()->MockOutput thread" + mockOutput).start();
|
||||
|
||||
sendJoinAcceptanceMessage(allocatedSourceID);
|
||||
|
||||
connectionState = ConnectionStateEnum.CONNECTED;
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Waits until the client sends a {@link RequestToJoin} message, and returns it.
|
||||
* @return The {@link RequestToJoin} message.
|
||||
* @throws HandshakeException Thrown if we get interrupted while waiting.
|
||||
*/
|
||||
private RequestToJoin waitForRequestToJoin() throws HandshakeException {
|
||||
|
||||
try {
|
||||
|
||||
|
||||
while (connectionState == ConnectionStateEnum.WAITING_FOR_HANDSHAKE) {
|
||||
|
||||
AC35Data message = inputQueue.take();
|
||||
|
||||
//We need to wait until they actually send a join request.
|
||||
if (message.getType() == MessageType.REQUEST_TO_JOIN) {
|
||||
return (RequestToJoin) message;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
} catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
throw new HandshakeException("Handshake failed. Thread: " + Thread.currentThread() + " was interrupted while waiting on the incoming message queue.", e);
|
||||
|
||||
}
|
||||
|
||||
|
||||
throw new HandshakeException("Handshake was cancelled. Connection state is now: " + connectionState);
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Sends the client a {@link JoinAcceptance} message, containing their assigned sourceID.
|
||||
* @param sourceID The sourceID to assign to client.
|
||||
* @throws HandshakeException Thrown if the thread is interrupted while placing message on the outgoing message queue.
|
||||
*/
|
||||
private void sendJoinAcceptanceMessage(int sourceID) throws HandshakeException {
|
||||
|
||||
//Send them the source ID.
|
||||
JoinAcceptance joinAcceptance = new JoinAcceptance(JoinAcceptanceEnum.JOIN_SUCCESSFUL, sourceID);
|
||||
|
||||
try {
|
||||
outputQueue.put(joinAcceptance);
|
||||
} catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
throw new HandshakeException("Handshake failed. Thread: " + Thread.currentThread() + " interrupted while placing JoinAcceptance message on outgoing message queue.", e);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Determines whether or not this connection is still alive.
|
||||
* This is based off whether the {@link MessageSerialiser} is still alive.
|
||||
* @return True if it is alive, false otherwise.
|
||||
*/
|
||||
public boolean isAlive() {
|
||||
return messageSerialiser.isRunning();
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,110 @@
|
||||
package mock.model;
|
||||
|
||||
import network.Messages.AC35Data;
|
||||
import network.Messages.HeartBeat;
|
||||
import shared.model.RunnableWithFramePeriod;
|
||||
|
||||
import java.util.concurrent.BlockingQueue;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
|
||||
/**
|
||||
* This class is responsible for sending {@link HeartBeat} messages to queue.
|
||||
*/
|
||||
public class HeartBeatService implements RunnableWithFramePeriod {
|
||||
|
||||
/**
|
||||
* Timestamp of the last sent heartbeat message.
|
||||
*/
|
||||
private long lastHeartbeatTime;
|
||||
|
||||
/**
|
||||
* Period for the heartbeat - that is, how often we send it. Milliseconds.
|
||||
*/
|
||||
private long heartbeatPeriod = 5000;
|
||||
|
||||
|
||||
/**
|
||||
* The messages we're writing to the stream.
|
||||
*/
|
||||
private BlockingQueue<AC35Data> messagesToSend;
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Sequence number for heartbeat messages.
|
||||
*/
|
||||
private int heartbeatSequenceNum = 1;
|
||||
|
||||
|
||||
/**
|
||||
* Constructs a new HeartBeatService to send heartBeat messages to a given outputStream.
|
||||
* @param messagesToSend The queue to send heartBeat messages to.
|
||||
*/
|
||||
public HeartBeatService(BlockingQueue<AC35Data> messagesToSend) {
|
||||
this.messagesToSend = messagesToSend;
|
||||
this.lastHeartbeatTime = System.currentTimeMillis();
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Increments the {@link #heartbeatSequenceNum} value, and returns it.
|
||||
* @return Incremented heat beat number.
|
||||
*/
|
||||
private int getNextHeartBeatNumber(){
|
||||
this.heartbeatSequenceNum++;
|
||||
|
||||
return this.heartbeatSequenceNum;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Generates the next heartbeat message and returns it. Increments the heartbeat sequence number.
|
||||
* @return The next heartbeat message.
|
||||
*/
|
||||
private HeartBeat createHeartbeatMessage() {
|
||||
|
||||
HeartBeat heartBeat = new HeartBeat(getNextHeartBeatNumber());
|
||||
|
||||
return heartBeat;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Puts a HeartBeat message on the message queue.
|
||||
* @throws InterruptedException Thrown if the thread is interrupted.
|
||||
*/
|
||||
private void sendHeartBeat() throws InterruptedException {
|
||||
|
||||
HeartBeat heartBeat = createHeartbeatMessage();
|
||||
|
||||
messagesToSend.put(heartBeat);
|
||||
}
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
|
||||
while (!Thread.interrupted()) {
|
||||
long currentFrameTime = System.currentTimeMillis();
|
||||
waitForFramePeriod(lastHeartbeatTime, currentFrameTime, heartbeatPeriod);
|
||||
lastHeartbeatTime = currentFrameTime;
|
||||
|
||||
try {
|
||||
sendHeartBeat();
|
||||
|
||||
} catch (InterruptedException e) {
|
||||
Logger.getGlobal().log(Level.WARNING, "HeartBeatService: " + this + " sendHeartBeat() was interrupted on thread: " + Thread.currentThread(), e);
|
||||
Thread.currentThread().interrupt();
|
||||
return;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -7,6 +7,7 @@ import shared.dataInput.BoatDataSource;
|
||||
import shared.dataInput.RaceDataSource;
|
||||
import network.Messages.Enums.RaceStatusEnum;
|
||||
import shared.dataInput.RegattaDataSource;
|
||||
import shared.exceptions.BoatNotFoundException;
|
||||
import shared.model.*;
|
||||
import shared.model.Bearing;
|
||||
|
||||
@@ -411,6 +412,25 @@ public class MockRace extends Race {
|
||||
return boats;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a boat by sourceID.
|
||||
* @param sourceID The source ID the boat.
|
||||
* @return The boat.
|
||||
* @throws BoatNotFoundException Thrown if there is not boat with the specified sourceID.
|
||||
*/
|
||||
public MockBoat getBoat(int sourceID) throws BoatNotFoundException {
|
||||
|
||||
for (MockBoat boat : boats) {
|
||||
|
||||
if (boat.getSourceID() == sourceID) {
|
||||
return boat;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
throw new BoatNotFoundException("Boat with sourceID: " + sourceID + " was not found.");
|
||||
}
|
||||
|
||||
/**
|
||||
* Changes the wind direction randomly, while keeping it within [windLowerBound, windUpperBound].
|
||||
*/
|
||||
|
||||
@@ -1,12 +1,15 @@
|
||||
package mock.model;
|
||||
|
||||
import javafx.animation.AnimationTimer;
|
||||
import mock.model.commandFactory.CompositeCommand;
|
||||
import network.Messages.Enums.BoatStatusEnum;
|
||||
import network.Messages.Enums.RaceStatusEnum;
|
||||
import network.Messages.LatestMessages;
|
||||
import shared.model.Race;
|
||||
|
||||
|
||||
|
||||
public class RaceLogic implements Runnable {
|
||||
|
||||
/**
|
||||
* State of current race modified by this object
|
||||
*/
|
||||
@@ -16,14 +19,18 @@ public class RaceLogic implements Runnable {
|
||||
*/
|
||||
private RaceServer server;
|
||||
|
||||
private CompositeCommand commands;
|
||||
|
||||
/**
|
||||
* Initialises race loop with state and server message queue
|
||||
* @param race state of race to modify
|
||||
* @param messages to send to server
|
||||
* @param compositeCommand Commands from clients to execute.
|
||||
*/
|
||||
public RaceLogic(MockRace race, LatestMessages messages) {
|
||||
public RaceLogic(MockRace race, LatestMessages messages, CompositeCommand compositeCommand) {
|
||||
this.race = race;
|
||||
this.server = new RaceServer(race, messages);
|
||||
this.commands = compositeCommand;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -56,17 +63,13 @@ public class RaceLogic implements Runnable {
|
||||
//Provide boat's with an estimated time at next mark until the race starts.
|
||||
race.setBoatsTimeNextMark(race.getRaceClock().getCurrentTime());
|
||||
|
||||
//Parse the boat locations.
|
||||
server.parseBoatLocations();
|
||||
|
||||
//Parse the marks.
|
||||
server.parseMarks();
|
||||
//Parse the race snapshot.
|
||||
server.parseSnapshot();
|
||||
|
||||
// Change wind direction
|
||||
race.changeWindDirection();
|
||||
|
||||
//Parse the race status.
|
||||
server.parseRaceStatus();
|
||||
|
||||
|
||||
|
||||
if (race.getRaceStatusEnum() == RaceStatusEnum.STARTED) {
|
||||
@@ -109,6 +112,9 @@ public class RaceLogic implements Runnable {
|
||||
//Get the current time.
|
||||
currentTime = System.currentTimeMillis();
|
||||
|
||||
//Execute commands from clients.
|
||||
commands.execute(race);
|
||||
|
||||
//Update race time.
|
||||
race.updateRaceTime(currentTime);
|
||||
|
||||
@@ -123,7 +129,6 @@ public class RaceLogic implements Runnable {
|
||||
|
||||
//If it is still racing, update its position.
|
||||
if (boat.getStatus() == BoatStatusEnum.RACING) {
|
||||
|
||||
race.updatePosition(boat, framePeriod, race.getRaceClock().getDurationMilli());
|
||||
|
||||
}
|
||||
@@ -141,15 +146,8 @@ public class RaceLogic implements Runnable {
|
||||
// Change wind direction
|
||||
race.changeWindDirection();
|
||||
|
||||
//Parse the boat locations.
|
||||
server.parseBoatLocations();
|
||||
|
||||
//Parse the marks.
|
||||
server.parseMarks();
|
||||
|
||||
//Parse the race status.
|
||||
server.parseRaceStatus();
|
||||
|
||||
//Parse the race snapshot.
|
||||
server.parseSnapshot();
|
||||
|
||||
//Update the last frame time.
|
||||
this.lastFrameTime = currentTime;
|
||||
@@ -165,7 +163,7 @@ public class RaceLogic implements Runnable {
|
||||
@Override
|
||||
public void handle(long now) {
|
||||
|
||||
server.parseRaceStatus();
|
||||
server.parseSnapshot();
|
||||
|
||||
if (iters > 500) {
|
||||
stop();
|
||||
@@ -173,4 +171,5 @@ public class RaceLogic implements Runnable {
|
||||
iters++;
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -1,10 +1,7 @@
|
||||
package mock.model;
|
||||
|
||||
import network.Messages.BoatLocation;
|
||||
import network.Messages.BoatStatus;
|
||||
import network.Messages.*;
|
||||
import network.Messages.Enums.BoatLocationDeviceEnum;
|
||||
import network.Messages.LatestMessages;
|
||||
import network.Messages.RaceStatus;
|
||||
import network.Utils.AC35UnitConverter;
|
||||
import shared.model.Bearing;
|
||||
import shared.model.CompoundMark;
|
||||
@@ -21,10 +18,6 @@ public class RaceServer {
|
||||
private MockRace race;
|
||||
private LatestMessages latestMessages;
|
||||
|
||||
/**
|
||||
* The sequence number of the latest RaceStatus message sent or received.
|
||||
*/
|
||||
private int raceStatusSequenceNumber = 1;
|
||||
|
||||
/**
|
||||
* The sequence number of the latest BoatLocation message sent or received.
|
||||
@@ -39,10 +32,31 @@ public class RaceServer {
|
||||
|
||||
|
||||
/**
|
||||
* Parses an individual marker boat, and sends it to mockOutput.
|
||||
* @param mark The marker boat to parse.
|
||||
* Parses the race to create a snapshot, and places it in latestMessages.
|
||||
*/
|
||||
private void parseIndividualMark(Mark mark) {
|
||||
public void parseSnapshot() {
|
||||
|
||||
List<AC35Data> snapshotMessages = new ArrayList<>();
|
||||
|
||||
//Parse the boat locations.
|
||||
snapshotMessages.addAll(parseBoatLocations());
|
||||
|
||||
//Parse the marks.
|
||||
snapshotMessages.addAll(parseMarks());
|
||||
|
||||
//Parse the race status.
|
||||
snapshotMessages.add(parseRaceStatus());
|
||||
|
||||
latestMessages.setSnapshot(snapshotMessages);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Parses an individual marker boat, and returns it.
|
||||
* @param mark The marker boat to parse.
|
||||
* @return The BoatLocation message.
|
||||
*/
|
||||
private BoatLocation parseIndividualMark(Mark mark) {
|
||||
//Create message.
|
||||
BoatLocation boatLocation = new BoatLocation(
|
||||
mark.getSourceID(),
|
||||
@@ -57,13 +71,17 @@ public class RaceServer {
|
||||
//Iterates the sequence number.
|
||||
this.boatLocationSequenceNumber++;
|
||||
|
||||
this.latestMessages.setBoatLocation(boatLocation);
|
||||
return boatLocation;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse the compound marker boats through mock output.
|
||||
* Parse the compound marker boats, and returns a list of BoatLocation messages.
|
||||
* @return BoatLocation messages for each mark.
|
||||
*/
|
||||
public void parseMarks() {
|
||||
private List<BoatLocation> parseMarks() {
|
||||
|
||||
List<BoatLocation> markLocations = new ArrayList<>(race.getCompoundMarks().size());
|
||||
|
||||
for (CompoundMark compoundMark : race.getCompoundMarks()) {
|
||||
|
||||
//Get the individual marks from the compound mark.
|
||||
@@ -72,31 +90,40 @@ public class RaceServer {
|
||||
|
||||
//If they aren't null, parse them (some compound marks only have one mark).
|
||||
if (mark1 != null) {
|
||||
this.parseIndividualMark(mark1);
|
||||
markLocations.add(this.parseIndividualMark(mark1));
|
||||
}
|
||||
|
||||
if (mark2 != null) {
|
||||
this.parseIndividualMark(mark2);
|
||||
markLocations.add(this.parseIndividualMark(mark2));
|
||||
}
|
||||
}
|
||||
|
||||
return markLocations;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Parse the boats in the race, and send it to mockOutput.
|
||||
* Parse the boats in the race, and returns all of their BoatLocation messages.
|
||||
* @return List of BoatLocation messages, for each boat.
|
||||
*/
|
||||
public void parseBoatLocations() {
|
||||
private List<BoatLocation> parseBoatLocations() {
|
||||
|
||||
List<BoatLocation> boatLocations = new ArrayList<>(race.getBoats().size());
|
||||
|
||||
//Parse each boat.
|
||||
for (MockBoat boat : race.getBoats()) {
|
||||
this.parseIndividualBoatLocation(boat);
|
||||
boatLocations.add(this.parseIndividualBoatLocation(boat));
|
||||
}
|
||||
|
||||
return boatLocations;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses an individual boat, and sends it to mockOutput.
|
||||
* Parses an individual boat, and returns it.
|
||||
* @param boat The boat to parse.
|
||||
* @return The BoatLocation message.
|
||||
*/
|
||||
private void parseIndividualBoatLocation(MockBoat boat) {
|
||||
private BoatLocation parseIndividualBoatLocation(MockBoat boat) {
|
||||
|
||||
BoatLocation boatLocation = new BoatLocation(
|
||||
boat.getSourceID(),
|
||||
@@ -111,16 +138,17 @@ public class RaceServer {
|
||||
//Iterates the sequence number.
|
||||
this.boatLocationSequenceNumber++;
|
||||
|
||||
this.latestMessages.setBoatLocation(boatLocation);
|
||||
return boatLocation;
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Parses the race status, and sends it to mockOutput.
|
||||
* Parses the race status, and returns it.
|
||||
* @return The race status message.
|
||||
*/
|
||||
public void parseRaceStatus() {
|
||||
private RaceStatus parseRaceStatus() {
|
||||
|
||||
//A race status message contains a list of boat statuses.
|
||||
List<BoatStatus> boatStatuses = new ArrayList<>();
|
||||
@@ -151,6 +179,6 @@ public class RaceServer {
|
||||
race.getRaceType(),
|
||||
boatStatuses);
|
||||
|
||||
this.latestMessages.setRaceStatus(raceStatus);
|
||||
return raceStatus;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,70 @@
|
||||
package mock.model;
|
||||
|
||||
|
||||
import mock.exceptions.SourceIDAllocationException;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* This class is responsible for allocating boat source IDs for use in a race, upon request.
|
||||
*/
|
||||
public class SourceIdAllocator {
|
||||
|
||||
|
||||
/**
|
||||
* This list contains all unallocated source IDs.
|
||||
*/
|
||||
List<Integer> unallocatedIDs = new ArrayList<>();
|
||||
|
||||
|
||||
/**
|
||||
* This list contains all allocated source IDs.
|
||||
*/
|
||||
List<Integer> allocatedIDs = new ArrayList<>();
|
||||
|
||||
|
||||
/**
|
||||
* Creates a source ID allocator, using the given list of unallocated source IDs.
|
||||
* @param unallocatedIDs List of unallocated source IDs.
|
||||
*/
|
||||
public SourceIdAllocator(List<Integer> unallocatedIDs) {
|
||||
//We need to copy the list.
|
||||
this.unallocatedIDs.addAll(unallocatedIDs);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Allocates a source ID for a boat.
|
||||
* @return The allocated source ID.
|
||||
* @throws SourceIDAllocationException Thrown if we cannot allocate any more source IDs.
|
||||
*/
|
||||
public synchronized int allocateSourceID() throws SourceIDAllocationException {
|
||||
|
||||
if (!unallocatedIDs.isEmpty()) {
|
||||
|
||||
int sourceID = unallocatedIDs.remove(0);
|
||||
|
||||
allocatedIDs.add(sourceID);
|
||||
|
||||
return sourceID;
|
||||
|
||||
} else {
|
||||
throw new SourceIDAllocationException("Could not allocate a source ID.");
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns a source ID to the source ID allocator, so that it can be reused.
|
||||
* @param sourceID Source ID to return.
|
||||
*/
|
||||
public void returnSourceID(Integer sourceID) {
|
||||
|
||||
//We remove an Integer, not an int, so that we remove by value not by index.
|
||||
allocatedIDs.remove(sourceID);
|
||||
|
||||
unallocatedIDs.add(sourceID);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
package mock.model.commandFactory;
|
||||
|
||||
import mock.model.MockRace;
|
||||
|
||||
import java.util.Stack;
|
||||
|
||||
/**
|
||||
* Wraps multiple commands into a composite to execute queued commands during a frame.
|
||||
*/
|
||||
public class CompositeCommand implements Command {
|
||||
private Stack<Command> commands;
|
||||
|
||||
public CompositeCommand() {
|
||||
this.commands = new Stack<>();
|
||||
}
|
||||
|
||||
public void addCommand(Command command) {
|
||||
commands.push(command);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute(MockRace race) {
|
||||
while(!commands.isEmpty()) commands.pop().execute(race);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user