# Conflicts: # racevisionGame/src/main/java/mock/app/MockOutput.java # racevisionGame/src/main/java/mock/model/MockRace.java # racevisionGame/src/main/java/mock/model/commandFactory/CommandFactory.java # racevisionGame/src/main/java/network/MessageDecoders/BoatActionDecoder.java # racevisionGame/src/main/java/network/MessageDecoders/BoatLocationDecoder.java # racevisionGame/src/main/java/visualiser/app/VisualiserInput.java # racevisionGame/src/main/java/visualiser/gameController/ControllerServer.java # racevisionGame/src/main/java/visualiser/model/ResizableRaceCanvas.java # racevisionGame/src/main/java/visualiser/model/VisualiserRace.java # racevisionGame/src/main/resources/visualiser/scenes/connect.fxml # racevisionGame/src/main/resources/visualiser/scenes/lobby.fxml # racevisionGame/src/test/java/mock/model/MockRaceTest.java # racevisionGame/src/test/java/network/Utils/AC35UnitConverterTest.java # racevisionGame/src/test/java/network/XMLMessageEncoderTest.javamain
commit
f60809bdd4
@ -1,6 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<project version="4">
|
|
||||||
<component name="ProjectCodeStyleSettingsManager">
|
|
||||||
<option name="PREFERRED_PROJECT_CODE_STYLE" value="Default" />
|
|
||||||
</component>
|
|
||||||
</project>
|
|
||||||
@ -0,0 +1,95 @@
|
|||||||
|
package mock.enums;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The states in which a connection to a client may have.
|
||||||
|
*/
|
||||||
|
public enum ConnectionStateEnum {
|
||||||
|
|
||||||
|
UNKNOWN(0),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* We're waiting for the client to complete the joining handshake (see {@link network.Messages.RequestToJoin}.
|
||||||
|
*/
|
||||||
|
WAITING_FOR_HANDSHAKE(1),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The server has receved a {@link network.Messages.RequestToJoin} from the client.
|
||||||
|
*/
|
||||||
|
REQUEST_RECEIVED(2),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The client has completed the handshake, and is connected.
|
||||||
|
* That is, the client sent a {@link network.Messages.RequestToJoin}, which was successful, and the server responded with a {@link network.Messages.JoinAcceptance}.
|
||||||
|
*/
|
||||||
|
CONNECTED(3),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The client has timed out.
|
||||||
|
*/
|
||||||
|
TIMED_OUT(4),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The client's connection has been declined.
|
||||||
|
*/
|
||||||
|
DECLINED(5);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
private byte value;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ctor. Creates a ConnectionStateEnum from a given primitive integer value, cast to a byte.
|
||||||
|
* @param value Integer, which is cast to byte, to construct from.
|
||||||
|
*/
|
||||||
|
private ConnectionStateEnum(int value) {
|
||||||
|
this.value = (byte) value;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the primitive value of the enum.
|
||||||
|
* @return Primitive value of the enum.
|
||||||
|
*/
|
||||||
|
public byte getValue() {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stores a mapping between Byte values and ConnectionStateEnum values.
|
||||||
|
*/
|
||||||
|
private static final Map<Byte, ConnectionStateEnum> byteToStatusMap = new HashMap<>();
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
Static initialization block. Initializes the byteToStatusMap.
|
||||||
|
*/
|
||||||
|
static {
|
||||||
|
for (ConnectionStateEnum type : ConnectionStateEnum.values()) {
|
||||||
|
ConnectionStateEnum.byteToStatusMap.put(type.value, type);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the enumeration value which corresponds to a given byte value.
|
||||||
|
* @param connectionState Byte value to convert to a ConnectionStateEnum value.
|
||||||
|
* @return The ConnectionStateEnum value which corresponds to the given byte value.
|
||||||
|
*/
|
||||||
|
public static ConnectionStateEnum fromByte(byte connectionState) {
|
||||||
|
//Gets the corresponding MessageType from the map.
|
||||||
|
ConnectionStateEnum type = ConnectionStateEnum.byteToStatusMap.get(connectionState);
|
||||||
|
|
||||||
|
if (type == null) {
|
||||||
|
//If the byte value wasn't found, return the UNKNOWN connectionState.
|
||||||
|
return ConnectionStateEnum.UNKNOWN;
|
||||||
|
} else {
|
||||||
|
//Otherwise, return the connectionState.
|
||||||
|
return type;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,24 @@
|
|||||||
|
package mock.exceptions;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An exception thrown when we cannot create a command for some reasn (e.g., uknown action type).
|
||||||
|
*/
|
||||||
|
public class CommandConstructionException extends Exception {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs the exception with a given message.
|
||||||
|
* @param message Message to store.
|
||||||
|
*/
|
||||||
|
public CommandConstructionException(String message) {
|
||||||
|
super(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs the exception with a given message and cause.
|
||||||
|
* @param message Message to store.
|
||||||
|
* @param cause Cause to store.
|
||||||
|
*/
|
||||||
|
public CommandConstructionException(String message, Throwable cause) {
|
||||||
|
super(message, cause);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,24 @@
|
|||||||
|
package mock.exceptions;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An exception thrown when we cannot create an {@link mock.app.Event}.
|
||||||
|
*/
|
||||||
|
public class EventConstructionException extends Exception {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs the exception with a given message.
|
||||||
|
* @param message Message to store.
|
||||||
|
*/
|
||||||
|
public EventConstructionException(String message) {
|
||||||
|
super(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs the exception with a given message and cause.
|
||||||
|
* @param message Message to store.
|
||||||
|
* @param cause Cause to store.
|
||||||
|
*/
|
||||||
|
public EventConstructionException(String message, Throwable cause) {
|
||||||
|
super(message, cause);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,24 @@
|
|||||||
|
package mock.exceptions;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An exception thrown when we cannot allocate a source ID.
|
||||||
|
*/
|
||||||
|
public class SourceIDAllocationException extends Exception {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs the exception with a given message.
|
||||||
|
* @param message Message to store.
|
||||||
|
*/
|
||||||
|
public SourceIDAllocationException(String message) {
|
||||||
|
super(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs the exception with a given message and cause.
|
||||||
|
* @param message Message to store.
|
||||||
|
* @param cause Cause to store.
|
||||||
|
*/
|
||||||
|
public SourceIDAllocationException(String message, Throwable cause) {
|
||||||
|
super(message, cause);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,288 @@
|
|||||||
|
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;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The thread the {@link HeartBeatService} runs on.
|
||||||
|
*/
|
||||||
|
private Thread heartBeatThread;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The race the client is connected to.
|
||||||
|
*/
|
||||||
|
private RaceLogic raceLogic;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Used to send the race snapshot to client.
|
||||||
|
*/
|
||||||
|
private MockOutput mockOutput;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The thread the {@link MockOutput} runs on.
|
||||||
|
*/
|
||||||
|
private Thread mockOutputThread;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Used to receive client input, and turn it into commands.
|
||||||
|
*/
|
||||||
|
private ControllerServer controllerServer;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The thread the {@link ControllerServer} runs on.
|
||||||
|
*/
|
||||||
|
private Thread controllerServerThread;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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.
|
||||||
|
* @param raceLogic The race the client is connected to.
|
||||||
|
* @throws IOException Thrown if there is a problem with the client socket.
|
||||||
|
*/
|
||||||
|
public ClientConnection(Socket socket, SourceIdAllocator sourceIdAllocator, LatestMessages latestMessages, CompositeCommand compositeCommand, RaceLogic raceLogic) throws IOException {
|
||||||
|
this.socket = socket;
|
||||||
|
this.sourceIdAllocator = sourceIdAllocator;
|
||||||
|
this.latestMessages = latestMessages;
|
||||||
|
this.compositeCommand = compositeCommand;
|
||||||
|
this.raceLogic = raceLogic;
|
||||||
|
|
||||||
|
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);
|
||||||
|
this.heartBeatThread = new Thread(heartBeatService, "ClientConnection()->HeartBeatService thread " + heartBeatService);
|
||||||
|
this.heartBeatThread.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, raceLogic.getRace());
|
||||||
|
this.controllerServerThread = new Thread(controllerServer, "ClientConnection.run()->ControllerServer thread" + controllerServer);
|
||||||
|
this.controllerServerThread.start();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
sendJoinAcceptanceMessage(allocatedSourceID);
|
||||||
|
|
||||||
|
this.mockOutput = new MockOutput(latestMessages, outputQueue);
|
||||||
|
this.mockOutputThread = new Thread(mockOutput, "ClientConnection.run()->MockOutput thread" + mockOutput);
|
||||||
|
this.mockOutputThread.start();
|
||||||
|
|
||||||
|
|
||||||
|
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_PARTICIPANT, 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();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Terminates this connection.
|
||||||
|
*/
|
||||||
|
public void terminate() {
|
||||||
|
|
||||||
|
if (this.heartBeatThread != null) {
|
||||||
|
this.heartBeatThread.interrupt();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.mockOutputThread != null) {
|
||||||
|
this.mockOutputThread.interrupt();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.controllerServerThread != null) {
|
||||||
|
this.controllerServerThread.interrupt();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@ -0,0 +1,54 @@
|
|||||||
|
package mock.model;
|
||||||
|
|
||||||
|
|
||||||
|
import shared.model.Bearing;
|
||||||
|
import shared.model.Wind;
|
||||||
|
|
||||||
|
import java.util.Random;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This class generates Wind objects for use in a MockRace.
|
||||||
|
* Initialised with a baseline wind speed and direction, and keeps it constant.
|
||||||
|
*/
|
||||||
|
public class ConstantWindGenerator implements WindGenerator {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The bearing the wind direction starts at.
|
||||||
|
*/
|
||||||
|
private Bearing windBaselineBearing;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The speed the wind starts at, in knots.
|
||||||
|
*/
|
||||||
|
private double windBaselineSpeed;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a constant wind generator, with a baseline wind speed and direction.
|
||||||
|
* @param windBaselineBearing Baseline wind direction.
|
||||||
|
* @param windBaselineSpeed Baseline wind speed, in knots.
|
||||||
|
*/
|
||||||
|
public ConstantWindGenerator(Bearing windBaselineBearing, double windBaselineSpeed) {
|
||||||
|
|
||||||
|
this.windBaselineBearing = windBaselineBearing;
|
||||||
|
this.windBaselineSpeed = windBaselineSpeed;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Wind generateBaselineWind() {
|
||||||
|
return new Wind(windBaselineBearing, windBaselineSpeed);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Wind generateNextWind(Wind currentWind) {
|
||||||
|
|
||||||
|
return generateBaselineWind();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@ -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 = 2500;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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;
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,242 @@
|
|||||||
|
package mock.model;
|
||||||
|
|
||||||
|
|
||||||
|
import shared.model.Bearing;
|
||||||
|
import shared.model.Wind;
|
||||||
|
|
||||||
|
import java.util.Random;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This class generates Wind objects for use in a MockRace.
|
||||||
|
* Bounds on bearing and speed can be specified.
|
||||||
|
* Wind can be completely random, or random incremental change.
|
||||||
|
*/
|
||||||
|
public class RandomWindGenerator implements WindGenerator {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The bearing the wind direction starts at.
|
||||||
|
*/
|
||||||
|
private Bearing windBaselineBearing;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The lower bearing angle that the wind may have.
|
||||||
|
*/
|
||||||
|
private Bearing windBearingLowerBound;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The upper bearing angle that the wind may have.
|
||||||
|
*/
|
||||||
|
private Bearing windBearingUpperBound;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The speed the wind starts at, in knots.
|
||||||
|
*/
|
||||||
|
private double windBaselineSpeed;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The lower speed that the wind may have, in knots.
|
||||||
|
*/
|
||||||
|
private double windSpeedLowerBound;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The upper speed that the wind may have, in knots.
|
||||||
|
*/
|
||||||
|
private double windSpeedUpperBound;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a wind generator, with a baseline, lower bound, and upper bound, for the wind speed and direction.
|
||||||
|
* @param windBaselineBearing Baseline wind direction.
|
||||||
|
* @param windBearingLowerBound Lower bound for wind direction.
|
||||||
|
* @param windBearingUpperBound Upper bound for wind direction.
|
||||||
|
* @param windBaselineSpeed Baseline wind speed, in knots.
|
||||||
|
* @param windSpeedLowerBound Lower bound for wind speed, in knots.
|
||||||
|
* @param windSpeedUpperBound Upper bound for wind speed, in knots.
|
||||||
|
*/
|
||||||
|
public RandomWindGenerator(Bearing windBaselineBearing, Bearing windBearingLowerBound, Bearing windBearingUpperBound, double windBaselineSpeed, double windSpeedLowerBound, double windSpeedUpperBound) {
|
||||||
|
|
||||||
|
this.windBaselineBearing = windBaselineBearing;
|
||||||
|
this.windBearingLowerBound = windBearingLowerBound;
|
||||||
|
this.windBearingUpperBound = windBearingUpperBound;
|
||||||
|
this.windBaselineSpeed = windBaselineSpeed;
|
||||||
|
this.windSpeedLowerBound = windSpeedLowerBound;
|
||||||
|
this.windSpeedUpperBound = windSpeedUpperBound;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Wind generateBaselineWind() {
|
||||||
|
return new Wind(windBaselineBearing, windBaselineSpeed);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generates a random Wind object, that is within the provided bounds.
|
||||||
|
* @return Generated wind object.
|
||||||
|
*/
|
||||||
|
public Wind generateRandomWind() {
|
||||||
|
|
||||||
|
double windSpeed = generateRandomWindSpeed();
|
||||||
|
Bearing windBearing = generateRandomWindBearing();
|
||||||
|
|
||||||
|
return new Wind(windBearing, windSpeed);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generates a random wind speed within the specified bounds. In knots.
|
||||||
|
* @return Wind speed, in knots.
|
||||||
|
*/
|
||||||
|
private double generateRandomWindSpeed() {
|
||||||
|
|
||||||
|
double randomSpeedKnots = generateRandomValueInBounds(windSpeedLowerBound, windSpeedUpperBound);
|
||||||
|
|
||||||
|
return randomSpeedKnots;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generates a random wind bearing within the specified bounds.
|
||||||
|
* @return Wind bearing.
|
||||||
|
*/
|
||||||
|
private Bearing generateRandomWindBearing() {
|
||||||
|
|
||||||
|
double randomBearingDegrees = generateRandomValueInBounds(windBearingLowerBound.degrees(), windBearingUpperBound.degrees());
|
||||||
|
|
||||||
|
return Bearing.fromDegrees(randomBearingDegrees);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generates a random value within a specified interval.
|
||||||
|
* @param lowerBound The lower bound of the interval.
|
||||||
|
* @param upperBound The upper bound of the interval.
|
||||||
|
* @return A random value within the interval.
|
||||||
|
*/
|
||||||
|
private static double generateRandomValueInBounds(double lowerBound, double upperBound) {
|
||||||
|
|
||||||
|
float proportion = new Random().nextFloat();
|
||||||
|
|
||||||
|
double delta = upperBound - lowerBound;
|
||||||
|
|
||||||
|
double amount = delta * proportion;
|
||||||
|
|
||||||
|
double finalAmount = amount + lowerBound;
|
||||||
|
|
||||||
|
return finalAmount;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generates a new value within an interval, given a start value, chance to change, and change amount.
|
||||||
|
* @param lowerBound Lower bound of interval.
|
||||||
|
* @param upperBound Upper bound of interval.
|
||||||
|
* @param currentValue The current value to change.
|
||||||
|
* @param changeAmount The amount to change by.
|
||||||
|
* @param chanceToChange The change to actually change the value.
|
||||||
|
* @return The new value.
|
||||||
|
*/
|
||||||
|
private static double generateNextValueInBounds(double lowerBound, double upperBound, double currentValue, double changeAmount, double chanceToChange) {
|
||||||
|
|
||||||
|
float chance = new Random().nextFloat();
|
||||||
|
|
||||||
|
|
||||||
|
if (chance <= chanceToChange) {
|
||||||
|
currentValue += changeAmount;
|
||||||
|
|
||||||
|
} else if (chance <= (2 * chanceToChange)) {
|
||||||
|
currentValue -= changeAmount;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
currentValue = clamp(lowerBound, upperBound, currentValue);
|
||||||
|
|
||||||
|
return currentValue;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Wind generateNextWind(Wind currentWind) {
|
||||||
|
|
||||||
|
double windSpeed = generateNextWindSpeed(currentWind.getWindSpeed());
|
||||||
|
Bearing windBearing = generateNextWindBearing(currentWind.getWindDirection());
|
||||||
|
|
||||||
|
return new Wind(windBearing, windSpeed);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generates the next wind speed to use.
|
||||||
|
* @param windSpeed Current wind speed, in knots.
|
||||||
|
* @return Next wind speed, in knots.
|
||||||
|
*/
|
||||||
|
private double generateNextWindSpeed(double windSpeed) {
|
||||||
|
|
||||||
|
double chanceToChange = 0.2;
|
||||||
|
double changeAmount = 0.1;
|
||||||
|
|
||||||
|
double nextWindSpeed = generateNextValueInBounds(
|
||||||
|
windSpeedLowerBound,
|
||||||
|
windSpeedUpperBound,
|
||||||
|
windSpeed,
|
||||||
|
changeAmount,
|
||||||
|
chanceToChange);
|
||||||
|
|
||||||
|
return nextWindSpeed;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generates the next wind speed to use.
|
||||||
|
* @param windBearing Current wind bearing.
|
||||||
|
* @return Next wind speed.
|
||||||
|
*/
|
||||||
|
private Bearing generateNextWindBearing(Bearing windBearing) {
|
||||||
|
|
||||||
|
double chanceToChange = 0.2;
|
||||||
|
double changeAmount = 0.5;
|
||||||
|
|
||||||
|
double nextWindBearingDegrees = generateNextValueInBounds(
|
||||||
|
windBearingLowerBound.degrees(),
|
||||||
|
windBearingUpperBound.degrees(),
|
||||||
|
windBearing.degrees(),
|
||||||
|
changeAmount,
|
||||||
|
chanceToChange);
|
||||||
|
|
||||||
|
return Bearing.fromDegrees(nextWindBearingDegrees);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clamps a value to be within an interval.
|
||||||
|
* @param lower Lower bound of the interval.
|
||||||
|
* @param upper Upper bound of the interval.
|
||||||
|
* @param value Value to clamp.
|
||||||
|
* @return The clamped value.
|
||||||
|
*/
|
||||||
|
private static double clamp(double lower, double upper, double value) {
|
||||||
|
|
||||||
|
if (value > upper) {
|
||||||
|
value = upper;
|
||||||
|
|
||||||
|
} else if (value < lower) {
|
||||||
|
value = lower;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,555 +0,0 @@
|
|||||||
//package mock.model;
|
|
||||||
//
|
|
||||||
//import javafx.animation.AnimationTimer;
|
|
||||||
//import network.Messages.BoatLocation;
|
|
||||||
//import network.Messages.BoatStatus;
|
|
||||||
//import network.Messages.Enums.BoatStatusEnum;
|
|
||||||
//import network.Messages.Enums.RaceStatusEnum;
|
|
||||||
//import network.Messages.LatestMessages;
|
|
||||||
//import network.Messages.RaceStatus;
|
|
||||||
//import network.Utils.AC35UnitConverter;
|
|
||||||
//import shared.dataInput.BoatDataSource;
|
|
||||||
//import shared.dataInput.RaceDataSource;
|
|
||||||
//import shared.dataInput.RegattaDataSource;
|
|
||||||
//import shared.model.*;
|
|
||||||
//
|
|
||||||
//import java.time.ZonedDateTime;
|
|
||||||
//import java.time.temporal.ChronoUnit;
|
|
||||||
//import java.util.ArrayList;
|
|
||||||
//import java.util.Iterator;
|
|
||||||
//import java.util.List;
|
|
||||||
//import java.util.Map;
|
|
||||||
//
|
|
||||||
//import static java.lang.Math.cos;
|
|
||||||
//
|
|
||||||
///**
|
|
||||||
// * Unused class, copy of MockRace so methods can be deleted once they are moved (more of a checklist)
|
|
||||||
// */
|
|
||||||
//public class SplitTODO {
|
|
||||||
//
|
|
||||||
//
|
|
||||||
// /**
|
|
||||||
// * Represents a yacht race.
|
|
||||||
// * Has a course, boats, boundaries, etc...
|
|
||||||
// * Is responsible for simulating the race, and sending messages to a MockOutput instance.
|
|
||||||
// */
|
|
||||||
// public class MockRace extends Race {
|
|
||||||
//
|
|
||||||
// /**
|
|
||||||
// * Constructs a race object with a given RaceDataSource, BoatDataSource, and RegattaDataSource and sends events to the given mockOutput.
|
|
||||||
// * @param boatDataSource Data source for boat related data (yachts and marker boats).
|
|
||||||
// * @param raceDataSource Data source for race related data (participating boats, legs, etc...).
|
|
||||||
// * @param regattaDataSource Data source for race related data (course name, location, timezone, etc...).
|
|
||||||
// * @param latestMessages The LatestMessages to send events to.
|
|
||||||
// * @param polars The polars table to be used for boat simulation.
|
|
||||||
// * @param timeScale The timeScale for the race. See {@link Constants#RaceTimeScale}.
|
|
||||||
// */
|
|
||||||
// public MockRace(BoatDataSource boatDataSource, RaceDataSource raceDataSource, RegattaDataSource regattaDataSource, LatestMessages latestMessages, Polars polars, int timeScale) {
|
|
||||||
//
|
|
||||||
// super(boatDataSource, raceDataSource, regattaDataSource, latestMessages);
|
|
||||||
//
|
|
||||||
// this.scaleFactor = timeScale;
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
//
|
|
||||||
//
|
|
||||||
// /**
|
|
||||||
// * Parse the compound marker boats through mock output.
|
|
||||||
// */
|
|
||||||
// private void parseMarks() {
|
|
||||||
// for (CompoundMark compoundMark : this.compoundMarks) {
|
|
||||||
//
|
|
||||||
// //Get the individual marks from the compound mark.
|
|
||||||
// Mark mark1 = compoundMark.getMark1();
|
|
||||||
// Mark mark2 = compoundMark.getMark2();
|
|
||||||
//
|
|
||||||
// //If they aren't null, parse them (some compound marks only have one mark).
|
|
||||||
// if (mark1 != null) {
|
|
||||||
// this.parseIndividualMark(mark1);
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// if (mark2 != null) {
|
|
||||||
// this.parseIndividualMark(mark2);
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// /**
|
|
||||||
// * Parses an individual marker boat, and sends it to mockOutput.
|
|
||||||
// * @param mark The marker boat to parse.
|
|
||||||
// */
|
|
||||||
// private void parseIndividualMark(Mark mark) {
|
|
||||||
//
|
|
||||||
// //Create message.
|
|
||||||
// BoatLocation boatLocation = new BoatLocation(
|
|
||||||
// mark.getSourceID(),
|
|
||||||
// mark.getPosition().getLatitude(),
|
|
||||||
// mark.getPosition().getLongitude(),
|
|
||||||
// this.boatLocationSequenceNumber,
|
|
||||||
// 0, 0,
|
|
||||||
// this.raceClock.getCurrentTimeMilli());
|
|
||||||
//
|
|
||||||
// //Iterates the sequence number.
|
|
||||||
// this.boatLocationSequenceNumber++;
|
|
||||||
//
|
|
||||||
// this.latestMessages.setBoatLocation(boatLocation);
|
|
||||||
//
|
|
||||||
//
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// /**
|
|
||||||
// * Parse the boats in the race, and send it to mockOutput.
|
|
||||||
// */
|
|
||||||
// private void parseBoatLocations() {
|
|
||||||
//
|
|
||||||
// //Parse each boat.
|
|
||||||
// for (MockBoat boat : this.boats) {
|
|
||||||
//
|
|
||||||
// this.parseIndividualBoatLocation(boat);
|
|
||||||
//
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// /**
|
|
||||||
// * Parses an individual boat, and sends it to mockOutput.
|
|
||||||
// * @param boat The boat to parse.
|
|
||||||
// */
|
|
||||||
// private void parseIndividualBoatLocation(MockBoat boat) {
|
|
||||||
//
|
|
||||||
// BoatLocation boatLocation = new BoatLocation(
|
|
||||||
// boat.getSourceID(),
|
|
||||||
// boat.getCurrentPosition().getLatitude(),
|
|
||||||
// boat.getCurrentPosition().getLongitude(),
|
|
||||||
// this.boatLocationSequenceNumber,
|
|
||||||
// boat.getBearing().degrees(),
|
|
||||||
// boat.getCurrentSpeed(),
|
|
||||||
// this.raceClock.getCurrentTimeMilli());
|
|
||||||
//
|
|
||||||
// //Iterates the sequence number.
|
|
||||||
// this.boatLocationSequenceNumber++;
|
|
||||||
//
|
|
||||||
// this.latestMessages.setBoatLocation(boatLocation);
|
|
||||||
//
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
//
|
|
||||||
// /**
|
|
||||||
// * Updates the race time to a specified value, in milliseconds since the unix epoch.
|
|
||||||
// * @param currentTime Milliseconds since unix epoch.
|
|
||||||
// */
|
|
||||||
// private void updateRaceTime(long currentTime) {
|
|
||||||
// this.raceClock.setUTCTime(currentTime);
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
//
|
|
||||||
// /**
|
|
||||||
// * Updates the race status enumeration based on the current time.
|
|
||||||
// */
|
|
||||||
// private void updateRaceStatusEnum() {
|
|
||||||
//
|
|
||||||
// //The millisecond duration of the race. Negative means it hasn't started, so we flip sign.
|
|
||||||
// long timeToStart = - this.raceClock.getDurationMilli();
|
|
||||||
//
|
|
||||||
//
|
|
||||||
// if (timeToStart > Constants.RacePreStartTime) {
|
|
||||||
// //Time > 3 minutes is the prestart period.
|
|
||||||
// this.setRaceStatusEnum(RaceStatusEnum.PRESTART);
|
|
||||||
//
|
|
||||||
// } else if ((timeToStart <= Constants.RacePreStartTime) && (timeToStart >= Constants.RacePreparatoryTime)) {
|
|
||||||
// //Time between [1, 3] minutes is the warning period.
|
|
||||||
// this.setRaceStatusEnum(RaceStatusEnum.WARNING);
|
|
||||||
//
|
|
||||||
// } else if ((timeToStart <= Constants.RacePreparatoryTime) && (timeToStart > 0)) {
|
|
||||||
// //Time between (0, 1] minutes is the preparatory period.
|
|
||||||
// this.setRaceStatusEnum(RaceStatusEnum.PREPARATORY);
|
|
||||||
//
|
|
||||||
// } else {
|
|
||||||
// //Otherwise, the race has started!
|
|
||||||
// this.setRaceStatusEnum(RaceStatusEnum.STARTED);
|
|
||||||
//
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
//
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// /**
|
|
||||||
// * Parses the race status, and sends it to mockOutput.
|
|
||||||
// */
|
|
||||||
// private void parseRaceStatus() {
|
|
||||||
//
|
|
||||||
// //A race status message contains a list of boat statuses.
|
|
||||||
// List<BoatStatus> boatStatuses = new ArrayList<>();
|
|
||||||
//
|
|
||||||
// //Add each boat status to the status list.
|
|
||||||
// for (MockBoat boat : this.boats) {
|
|
||||||
//
|
|
||||||
// BoatStatus boatStatus = new BoatStatus(
|
|
||||||
// boat.getSourceID(),
|
|
||||||
// boat.getStatus(),
|
|
||||||
// boat.getCurrentLeg().getLegNumber(),
|
|
||||||
// boat.getEstimatedTimeAtNextMark().toInstant().toEpochMilli() );
|
|
||||||
//
|
|
||||||
// boatStatuses.add(boatStatus);
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
//
|
|
||||||
// //Convert wind direction and speed to ints. //TODO this conversion should be done inside the racestatus class.
|
|
||||||
// int windDirectionInt = AC35UnitConverter.encodeHeading(this.getWindDirection().degrees());
|
|
||||||
// int windSpeedInt = (int) (this.getWindSpeed() * Constants.KnotsToMMPerSecond);
|
|
||||||
//
|
|
||||||
// //Create race status object, and send it.
|
|
||||||
// RaceStatus raceStatus = new RaceStatus(
|
|
||||||
// System.currentTimeMillis(),
|
|
||||||
// this.raceId,
|
|
||||||
// this.getRaceStatusEnum().getValue(),
|
|
||||||
// this.raceClock.getStartingTimeMilli(),
|
|
||||||
// windDirectionInt,
|
|
||||||
// windSpeedInt,
|
|
||||||
// this.getRaceType().getValue(),
|
|
||||||
// boatStatuses);
|
|
||||||
//
|
|
||||||
//
|
|
||||||
// this.latestMessages.setRaceStatus(raceStatus);
|
|
||||||
//
|
|
||||||
//
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
//
|
|
||||||
// /**
|
|
||||||
// * Sets the status of all boats in the race to RACING.
|
|
||||||
// */
|
|
||||||
// private void setBoatsStatusToRacing() {
|
|
||||||
//
|
|
||||||
// for (MockBoat boat : this.boats) {
|
|
||||||
// boat.setStatus(BoatStatusEnum.RACING);
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
//
|
|
||||||
// /**
|
|
||||||
// * Sets the estimated time at next mark for each boat to a specified time. This is used during the countdown timer to provide this value to boat before the race starts.
|
|
||||||
// * @param time The time to provide to each boat.
|
|
||||||
// */
|
|
||||||
// private void setBoatsTimeNextMark(ZonedDateTime time) {
|
|
||||||
//
|
|
||||||
// for (MockBoat boat : this.boats) {
|
|
||||||
// boat.setEstimatedTimeAtNextMark(time);
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
//
|
|
||||||
// /**
|
|
||||||
// * Countdown timer until race starts.
|
|
||||||
// */
|
|
||||||
// protected AnimationTimer countdownTimer = new AnimationTimer() {
|
|
||||||
//
|
|
||||||
//
|
|
||||||
// long currentTime = System.currentTimeMillis();
|
|
||||||
//
|
|
||||||
// @Override
|
|
||||||
// public void handle(long arg0) {
|
|
||||||
//
|
|
||||||
// //Update race time.
|
|
||||||
// updateRaceTime(currentTime);
|
|
||||||
//
|
|
||||||
// //Update the race status based on the current time.
|
|
||||||
// updateRaceStatusEnum();
|
|
||||||
//
|
|
||||||
// //Provide boat's with an estimated time at next mark until the race starts.
|
|
||||||
// setBoatsTimeNextMark(raceClock.getCurrentTime());
|
|
||||||
//
|
|
||||||
// //Parse the boat locations.
|
|
||||||
// parseBoatLocations();
|
|
||||||
//
|
|
||||||
// //Parse the marks.
|
|
||||||
// parseMarks();
|
|
||||||
//
|
|
||||||
// // Change wind direction
|
|
||||||
// changeWindDirection();
|
|
||||||
//
|
|
||||||
// //Parse the race status.
|
|
||||||
// parseRaceStatus();
|
|
||||||
//
|
|
||||||
//
|
|
||||||
// if (getRaceStatusEnum() == RaceStatusEnum.STARTED) {
|
|
||||||
// setBoatsStatusToRacing();
|
|
||||||
// raceTimer.start();
|
|
||||||
// this.stop();
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// //Update the animations timer's time.
|
|
||||||
// currentTime = System.currentTimeMillis();
|
|
||||||
// }
|
|
||||||
// };
|
|
||||||
//
|
|
||||||
//
|
|
||||||
// /**
|
|
||||||
// * Timer that runs for the duration of the race, until all boats finish.
|
|
||||||
// */
|
|
||||||
// private AnimationTimer raceTimer = new AnimationTimer() {
|
|
||||||
//
|
|
||||||
// /**
|
|
||||||
// * Start time of loop, in milliseconds.
|
|
||||||
// */
|
|
||||||
// long timeRaceStarted = System.currentTimeMillis();
|
|
||||||
//
|
|
||||||
// /**
|
|
||||||
// * Current time during a loop iteration.
|
|
||||||
// */
|
|
||||||
// long currentTime = System.currentTimeMillis();
|
|
||||||
//
|
|
||||||
// /**
|
|
||||||
// * The time of the previous frame, in milliseconds.
|
|
||||||
// */
|
|
||||||
// long lastFrameTime = timeRaceStarted;
|
|
||||||
//
|
|
||||||
// @Override
|
|
||||||
// public void handle(long arg0) {
|
|
||||||
//
|
|
||||||
// //Get the current time.
|
|
||||||
// currentTime = System.currentTimeMillis();
|
|
||||||
//
|
|
||||||
// //Update race time.
|
|
||||||
// updateRaceTime(currentTime);
|
|
||||||
//
|
|
||||||
//
|
|
||||||
// //As long as there is at least one boat racing, we still simulate the race.
|
|
||||||
// if (getNumberOfActiveBoats() != 0) {
|
|
||||||
//
|
|
||||||
// //Get the time period of this frame.
|
|
||||||
// long framePeriod = currentTime - lastFrameTime;
|
|
||||||
//
|
|
||||||
// //For each boat, we update its position, and generate a BoatLocationMessage.
|
|
||||||
// for (MockBoat boat : boats) {
|
|
||||||
//
|
|
||||||
// //If it is still racing, update its position.
|
|
||||||
// if (boat.getStatus() == BoatStatusEnum.RACING) {
|
|
||||||
//
|
|
||||||
// updatePosition(boat, framePeriod, raceClock.getDurationMilli());
|
|
||||||
//
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// } else {
|
|
||||||
// //Otherwise, the race is over!
|
|
||||||
// raceFinished.start();
|
|
||||||
// setRaceStatusEnum(RaceStatusEnum.FINISHED);
|
|
||||||
// this.stop();
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// if (getNumberOfActiveBoats() != 0) {
|
|
||||||
// // Change wind direction
|
|
||||||
// changeWindDirection();
|
|
||||||
//
|
|
||||||
// //Parse the boat locations.
|
|
||||||
// parseBoatLocations();
|
|
||||||
//
|
|
||||||
// //Parse the marks.
|
|
||||||
// parseMarks();
|
|
||||||
//
|
|
||||||
// //Parse the race status.
|
|
||||||
// parseRaceStatus();
|
|
||||||
//
|
|
||||||
//
|
|
||||||
// //Update the last frame time.
|
|
||||||
// this.lastFrameTime = currentTime;
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// };
|
|
||||||
//
|
|
||||||
// /**
|
|
||||||
// * Broadcast that the race has finished.
|
|
||||||
// */
|
|
||||||
// protected AnimationTimer raceFinished = new AnimationTimer(){
|
|
||||||
// int iters = 0;
|
|
||||||
// @Override
|
|
||||||
// public void handle(long now) {
|
|
||||||
//
|
|
||||||
// parseRaceStatus();
|
|
||||||
//
|
|
||||||
// if (iters > 500) {
|
|
||||||
// stop();
|
|
||||||
// }
|
|
||||||
// iters++;
|
|
||||||
// }
|
|
||||||
// };
|
|
||||||
//
|
|
||||||
//
|
|
||||||
// /**
|
|
||||||
// * Calculates a boat's VMG.
|
|
||||||
// * @param boat The boat to calculate VMG for.
|
|
||||||
// * @return VMG for the specified boat.
|
|
||||||
// */
|
|
||||||
// private VMG calculateVMG(MockBoat boat) {
|
|
||||||
//
|
|
||||||
//
|
|
||||||
// //Find the VMG inside these bounds.
|
|
||||||
// VMG bestVMG = boat.getPolars().calculateVMG(this.getWindDirection(), this.getWindSpeed(), boat.calculateBearingToNextMarker(), Bearing.fromDegrees(0d), Bearing.fromDegrees(359.99999d));
|
|
||||||
//
|
|
||||||
//
|
|
||||||
// return bestVMG;
|
|
||||||
//
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
//
|
|
||||||
// /**
|
|
||||||
// * Determines whether or not a given VMG improves the velocity of a boat, if it were currently using currentVMG.
|
|
||||||
// * @param currentVMG The current VMG of the boat.
|
|
||||||
// * @param potentialVMG The new VMG to test.
|
|
||||||
// * @param bearingToDestination The bearing between the boat and its destination.
|
|
||||||
// * @return True if the new VMG is improves velocity, false otherwise.
|
|
||||||
// */
|
|
||||||
// private boolean improvesVelocity(VMG currentVMG, VMG potentialVMG, Bearing bearingToDestination) {
|
|
||||||
//
|
|
||||||
// //Calculates the angle between the boat and its destination.
|
|
||||||
// Angle angleBetweenDestAndHeading = Angle.fromDegrees(currentVMG.getBearing().degrees() - bearingToDestination.degrees());
|
|
||||||
//
|
|
||||||
// //Calculates the angle between the new VMG and the boat's destination.
|
|
||||||
// Angle angleBetweenDestAndNewVMG = Angle.fromDegrees(potentialVMG.getBearing().degrees() - bearingToDestination.degrees());
|
|
||||||
//
|
|
||||||
//
|
|
||||||
// //Calculate the boat's current velocity.
|
|
||||||
// double currentVelocity = Math.cos(angleBetweenDestAndHeading.radians()) * currentVMG.getSpeed();
|
|
||||||
//
|
|
||||||
// //Calculate the potential velocity with the new VMG.
|
|
||||||
// double vmgVelocity = Math.cos(angleBetweenDestAndNewVMG.radians()) * potentialVMG.getSpeed();
|
|
||||||
//
|
|
||||||
// //Return whether or not the new VMG gives better velocity.
|
|
||||||
// return vmgVelocity > currentVelocity;
|
|
||||||
//
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// /**
|
|
||||||
// * Determines whether or not a given VMG improves the velocity of a boat.
|
|
||||||
// * @param boat The boat to test.
|
|
||||||
// * @param vmg The new VMG to test.
|
|
||||||
// * @return True if the new VMG is improves velocity, false otherwise.
|
|
||||||
// */
|
|
||||||
// private boolean improvesVelocity(MockBoat boat, VMG vmg) {
|
|
||||||
//
|
|
||||||
// //Get the boats "current" VMG.
|
|
||||||
// VMG boatVMG = new VMG(boat.getCurrentSpeed(), boat.getBearing());
|
|
||||||
//
|
|
||||||
// //Check if the new VMG is better than the boat's current VMG.
|
|
||||||
// return this.improvesVelocity(boatVMG, vmg, boat.calculateBearingToNextMarker());
|
|
||||||
//
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
//
|
|
||||||
// /**
|
|
||||||
// * Calculates the distance a boat has travelled and updates its current position according to this value.
|
|
||||||
// *
|
|
||||||
// * @param boat The boat to be updated.
|
|
||||||
// * @param updatePeriodMilliseconds The time, in milliseconds, since the last update.
|
|
||||||
// * @param totalElapsedMilliseconds The total number of milliseconds that have elapsed since the start of the race.
|
|
||||||
// */
|
|
||||||
// protected void updatePosition(MockBoat boat, long updatePeriodMilliseconds, long totalElapsedMilliseconds) {
|
|
||||||
//
|
|
||||||
// //Checks if the current boat has finished the race or not.
|
|
||||||
// boolean finish = this.isLastLeg(boat.getCurrentLeg());
|
|
||||||
//
|
|
||||||
// if (!finish) {
|
|
||||||
//
|
|
||||||
//
|
|
||||||
// //Calculates the distance travelled, in meters, in the current timeslice.
|
|
||||||
// double distanceTravelledMeters = boat.calculateMetersTravelled(updatePeriodMilliseconds);
|
|
||||||
//
|
|
||||||
// //Scale it.
|
|
||||||
// distanceTravelledMeters = distanceTravelledMeters * this.scaleFactor;
|
|
||||||
//
|
|
||||||
//
|
|
||||||
// //Move the boat forwards that many meters, and advances its time counters by enough milliseconds.
|
|
||||||
// boat.moveForwards(distanceTravelledMeters, updatePeriodMilliseconds * this.scaleFactor);
|
|
||||||
//
|
|
||||||
// long tackPeriod = 15000;
|
|
||||||
// if (boat.getTimeSinceTackChange() > tackPeriod) {
|
|
||||||
// //Calculate the new VMG.
|
|
||||||
// VMG newVMG = this.calculateVMG(boat);
|
|
||||||
//
|
|
||||||
//
|
|
||||||
// //If the new vmg improves velocity, use it.
|
|
||||||
// if (improvesVelocity(boat, newVMG)) {
|
|
||||||
// boat.setVMG(newVMG);
|
|
||||||
//
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// this.updateEstimatedTime(boat);
|
|
||||||
//
|
|
||||||
//
|
|
||||||
// //Check the boats position (update leg and stuff).
|
|
||||||
// this.checkPosition(boat, totalElapsedMilliseconds);
|
|
||||||
//
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
//
|
|
||||||
// /**
|
|
||||||
// * Checks if a boat has finished any legs, or has pulled out of race (DNF).
|
|
||||||
// * @param boat The boat to check.
|
|
||||||
// * @param timeElapsed The total time, in milliseconds, that has elapsed since the race started.
|
|
||||||
// */
|
|
||||||
// protected void checkPosition(MockBoat boat, long timeElapsed) {
|
|
||||||
//
|
|
||||||
// //The distance, in nautical miles, within which the boat needs to get in order to consider that it has reached the marker.
|
|
||||||
// double epsilonNauticalMiles = 100.0 / Constants.NMToMetersConversion; //100 meters. TODO should be more like 5-10.
|
|
||||||
//
|
|
||||||
// if (boat.calculateDistanceToNextMarker() < epsilonNauticalMiles) {
|
|
||||||
// //Boat has reached its target marker, and has moved on to a new leg.
|
|
||||||
//
|
|
||||||
//
|
|
||||||
//
|
|
||||||
// //Calculate how much the boat overshot the marker by.
|
|
||||||
// double overshootMeters = boat.calculateDistanceToNextMarker();
|
|
||||||
//
|
|
||||||
//
|
|
||||||
// //Move boat on to next leg.
|
|
||||||
// Leg nextLeg = this.legs.get(boat.getCurrentLeg().getLegNumber() + 1);
|
|
||||||
// boat.setCurrentLeg(nextLeg);
|
|
||||||
//
|
|
||||||
// //Add overshoot distance into the distance travelled for the next leg.
|
|
||||||
// boat.setDistanceTravelledInLeg(overshootMeters);
|
|
||||||
//
|
|
||||||
// //Setting a high value for this allows the boat to immediately do a large turn, as it needs to in order to get to the next mark.
|
|
||||||
// boat.setTimeSinceTackChange(999999);
|
|
||||||
//
|
|
||||||
//
|
|
||||||
// //Check if the boat has finished or stopped racing.
|
|
||||||
//
|
|
||||||
// if (this.isLastLeg(boat.getCurrentLeg())) {
|
|
||||||
// //Boat has finished.
|
|
||||||
// boat.setTimeFinished(timeElapsed);
|
|
||||||
// boat.setCurrentSpeed(0);
|
|
||||||
// boat.setStatus(BoatStatusEnum.FINISHED);
|
|
||||||
//
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// /**
|
|
||||||
// * Updates the boat's estimated time to next mark if positive
|
|
||||||
// * @param boat to estimate time given its velocity
|
|
||||||
// */
|
|
||||||
// private void updateEstimatedTime(MockBoat boat) {
|
|
||||||
//
|
|
||||||
// double velocityToMark = boat.getCurrentSpeed() * cos(boat.getBearing().radians() - boat.calculateBearingToNextMarker().radians()) / Constants.KnotsToMMPerSecond;
|
|
||||||
//
|
|
||||||
// if (velocityToMark > 0) {
|
|
||||||
//
|
|
||||||
// //Calculate milliseconds until boat reaches mark.
|
|
||||||
// long timeFromNow = (long) (1000 * boat.calculateDistanceToNextMarker() / velocityToMark);
|
|
||||||
//
|
|
||||||
// //Calculate time at which it will reach mark.
|
|
||||||
// ZonedDateTime timeAtMark = this.raceClock.getCurrentTime().plus(timeFromNow, ChronoUnit.MILLIS);
|
|
||||||
// boat.setEstimatedTimeAtNextMark(timeAtMark);
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
//}
|
|
||||||
@ -1,249 +1,29 @@
|
|||||||
package mock.model;
|
package mock.model;
|
||||||
|
|
||||||
|
|
||||||
import shared.model.Bearing;
|
|
||||||
import shared.model.Wind;
|
import shared.model.Wind;
|
||||||
|
|
||||||
import java.util.Random;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This class generates Wind objects for use in a MockRace.
|
* Interface for wind generators. It allows for generating a baseline wind, and subsequent winds.
|
||||||
* Bounds on bearing and speed can be specified.
|
|
||||||
* Wind can be completely random, or random incremental change.
|
|
||||||
*/
|
*/
|
||||||
public class WindGenerator {
|
public interface WindGenerator {
|
||||||
|
|
||||||
/**
|
|
||||||
* The bearing the wind direction starts at.
|
|
||||||
*/
|
|
||||||
private Bearing windBaselineBearing;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The lower bearing angle that the wind may have.
|
|
||||||
*/
|
|
||||||
private Bearing windBearingLowerBound;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The upper bearing angle that the wind may have.
|
|
||||||
*/
|
|
||||||
private Bearing windBearingUpperBound;
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The speed the wind starts at, in knots.
|
|
||||||
*/
|
|
||||||
private double windBaselineSpeed;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The lower speed that the wind may have, in knots.
|
|
||||||
*/
|
|
||||||
private double windSpeedLowerBound;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The upper speed that the wind may have, in knots.
|
|
||||||
*/
|
|
||||||
private double windSpeedUpperBound;
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a wind generator, with a baseline, lower bound, and upper bound, for the wind speed and direction.
|
|
||||||
* @param windBaselineBearing Baseline wind direction.
|
|
||||||
* @param windBearingLowerBound Lower bound for wind direction.
|
|
||||||
* @param windBearingUpperBound Upper bound for wind direction.
|
|
||||||
* @param windBaselineSpeed Baseline wind speed, in knots.
|
|
||||||
* @param windSpeedLowerBound Lower bound for wind speed, in knots.
|
|
||||||
* @param windSpeedUpperBound Upper bound for wind speed, in knots.
|
|
||||||
*/
|
|
||||||
public WindGenerator(Bearing windBaselineBearing, Bearing windBearingLowerBound, Bearing windBearingUpperBound, double windBaselineSpeed, double windSpeedLowerBound, double windSpeedUpperBound) {
|
|
||||||
|
|
||||||
this.windBaselineBearing = windBaselineBearing;
|
|
||||||
this.windBearingLowerBound = windBearingLowerBound;
|
|
||||||
this.windBearingUpperBound = windBearingUpperBound;
|
|
||||||
this.windBaselineSpeed = windBaselineSpeed;
|
|
||||||
this.windSpeedLowerBound = windSpeedLowerBound;
|
|
||||||
this.windSpeedUpperBound = windSpeedUpperBound;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Generates a wind object using the baseline wind speed and bearing.
|
* Generates a wind object using the baseline wind speed and bearing.
|
||||||
* @return Baseline wind object.
|
* @return Baseline wind object.
|
||||||
*/
|
*/
|
||||||
public Wind generateBaselineWind() {
|
Wind generateBaselineWind();
|
||||||
return new Wind(windBaselineBearing, windBaselineSpeed);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Generates a random Wind object, that is within the provided bounds.
|
|
||||||
* @return Generated wind object.
|
|
||||||
*/
|
|
||||||
public Wind generateRandomWind() {
|
|
||||||
|
|
||||||
double windSpeed = generateRandomWindSpeed();
|
|
||||||
Bearing windBearing = generateRandomWindBearing();
|
|
||||||
|
|
||||||
return new Wind(windBearing, windSpeed);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Generates a random wind speed within the specified bounds. In knots.
|
|
||||||
* @return Wind speed, in knots.
|
|
||||||
*/
|
|
||||||
private double generateRandomWindSpeed() {
|
|
||||||
|
|
||||||
double randomSpeedKnots = generateRandomValueInBounds(windSpeedLowerBound, windSpeedUpperBound);
|
|
||||||
|
|
||||||
return randomSpeedKnots;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Generates a random wind bearing within the specified bounds.
|
|
||||||
* @return Wind bearing.
|
|
||||||
*/
|
|
||||||
private Bearing generateRandomWindBearing() {
|
|
||||||
|
|
||||||
double randomBearingDegrees = generateRandomValueInBounds(windBearingLowerBound.degrees(), windBearingUpperBound.degrees());
|
|
||||||
|
|
||||||
return Bearing.fromDegrees(randomBearingDegrees);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Generates a random value within a specified interval.
|
|
||||||
* @param lowerBound The lower bound of the interval.
|
|
||||||
* @param upperBound The upper bound of the interval.
|
|
||||||
* @return A random value within the interval.
|
|
||||||
*/
|
|
||||||
private static double generateRandomValueInBounds(double lowerBound, double upperBound) {
|
|
||||||
|
|
||||||
float proportion = new Random().nextFloat();
|
|
||||||
|
|
||||||
double delta = upperBound - lowerBound;
|
|
||||||
|
|
||||||
double amount = delta * proportion;
|
|
||||||
|
|
||||||
double finalAmount = amount + lowerBound;
|
|
||||||
|
|
||||||
return finalAmount;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Generates a new value within an interval, given a start value, chance to change, and change amount.
|
|
||||||
* @param lowerBound Lower bound of interval.
|
|
||||||
* @param upperBound Upper bound of interval.
|
|
||||||
* @param currentValue The current value to change.
|
|
||||||
* @param changeAmount The amount to change by.
|
|
||||||
* @param chanceToChange The change to actually change the value.
|
|
||||||
* @return The new value.
|
|
||||||
*/
|
|
||||||
private static double generateNextValueInBounds(double lowerBound, double upperBound, double currentValue, double changeAmount, double chanceToChange) {
|
|
||||||
|
|
||||||
float chance = new Random().nextFloat();
|
|
||||||
|
|
||||||
|
|
||||||
if (chance <= chanceToChange) {
|
|
||||||
currentValue += changeAmount;
|
|
||||||
|
|
||||||
} else if (chance <= (2 * chanceToChange)) {
|
|
||||||
currentValue -= changeAmount;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
currentValue = clamp(lowerBound, upperBound, currentValue);
|
|
||||||
|
|
||||||
return currentValue;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Generates the next Wind object, that is within the provided bounds. This randomly increases or decreases the wind's speed and bearing.
|
* Generates the next Wind object, according to the implementation of the wind generator.
|
||||||
* @param currentWind The current wind to change. This is not modified.
|
* @param currentWind The current wind to change. This is not modified.
|
||||||
* @return Generated wind object.
|
* @return Generated wind object.
|
||||||
*/
|
*/
|
||||||
public Wind generateNextWind(Wind currentWind) {
|
Wind generateNextWind(Wind currentWind);
|
||||||
|
|
||||||
double windSpeed = generateNextWindSpeed(currentWind.getWindSpeed());
|
|
||||||
Bearing windBearing = generateNextWindBearing(currentWind.getWindDirection());
|
|
||||||
|
|
||||||
return new Wind(windBearing, windSpeed);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Generates the next wind speed to use.
|
|
||||||
* @param windSpeed Current wind speed, in knots.
|
|
||||||
* @return Next wind speed, in knots.
|
|
||||||
*/
|
|
||||||
private double generateNextWindSpeed(double windSpeed) {
|
|
||||||
|
|
||||||
double chanceToChange = 0.2;
|
|
||||||
double changeAmount = 0.1;
|
|
||||||
|
|
||||||
double nextWindSpeed = generateNextValueInBounds(
|
|
||||||
windSpeedLowerBound,
|
|
||||||
windSpeedUpperBound,
|
|
||||||
windSpeed,
|
|
||||||
changeAmount,
|
|
||||||
chanceToChange);
|
|
||||||
|
|
||||||
return nextWindSpeed;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Generates the next wind speed to use.
|
|
||||||
* @param windBearing Current wind bearing.
|
|
||||||
* @return Next wind speed.
|
|
||||||
*/
|
|
||||||
private Bearing generateNextWindBearing(Bearing windBearing) {
|
|
||||||
|
|
||||||
double chanceToChange = 0.2;
|
|
||||||
double changeAmount = 0.5;
|
|
||||||
|
|
||||||
double nextWindBearingDegrees = generateNextValueInBounds(
|
|
||||||
windBearingLowerBound.degrees(),
|
|
||||||
windBearingUpperBound.degrees(),
|
|
||||||
windBearing.degrees(),
|
|
||||||
changeAmount,
|
|
||||||
chanceToChange);
|
|
||||||
|
|
||||||
return Bearing.fromDegrees(nextWindBearingDegrees);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Clamps a value to be within an interval.
|
|
||||||
* @param lower Lower bound of the interval.
|
|
||||||
* @param upper Upper bound of the interval.
|
|
||||||
* @param value Value to clamp.
|
|
||||||
* @return The clamped value.
|
|
||||||
*/
|
|
||||||
private static double clamp(double lower, double upper, double value) {
|
|
||||||
|
|
||||||
if (value > upper) {
|
|
||||||
value = upper;
|
|
||||||
|
|
||||||
} else if (value < lower) {
|
|
||||||
value = lower;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -0,0 +1,17 @@
|
|||||||
|
package network.Exceptions;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An exception thrown when we encounter a message type that isn't recognised.
|
||||||
|
*/
|
||||||
|
public class InvalidMessageTypeException extends Exception {
|
||||||
|
|
||||||
|
|
||||||
|
public InvalidMessageTypeException(String message) {
|
||||||
|
super(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
public InvalidMessageTypeException(String message, Throwable cause) {
|
||||||
|
super(message, cause);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,9 @@
|
|||||||
|
package network.MessageControllers;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
public class MessageController {
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
@ -1,56 +1,107 @@
|
|||||||
package network.MessageDecoders;
|
package network.MessageDecoders;
|
||||||
|
|
||||||
|
|
||||||
|
import network.Exceptions.InvalidMessageException;
|
||||||
|
import network.Messages.AC35Data;
|
||||||
import network.Messages.AverageWind;
|
import network.Messages.AverageWind;
|
||||||
import network.Utils.ByteConverter;
|
import network.Utils.ByteConverter;
|
||||||
|
|
||||||
|
import static network.Utils.AC35UnitConverter.*;
|
||||||
|
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Created by hba56 on 23/04/17.
|
* Decodes {@link AverageWind} messages.
|
||||||
*/
|
*/
|
||||||
public class AverageWindDecoder {
|
public class AverageWindDecoder implements MessageDecoder {
|
||||||
byte messageVersionNumber;
|
|
||||||
byte[] byteTime;
|
/**
|
||||||
byte[] byteRawPeriod;
|
* The encoded message.
|
||||||
byte[] byteRawSpeed;
|
*/
|
||||||
byte[] bytePeriod2;
|
private byte[] encodedMessage;
|
||||||
byte[] byteSpeed2;
|
|
||||||
byte[] bytePeriod3;
|
/**
|
||||||
byte[] byteSpeed3;
|
* The decoded message.
|
||||||
byte[] bytePeriod4;
|
*/
|
||||||
byte[] byteSpeed4;
|
private AverageWind message;
|
||||||
|
|
||||||
AverageWind averageWind;
|
|
||||||
|
|
||||||
public AverageWindDecoder(byte[] encodedAverageWind) {
|
public AverageWindDecoder() {
|
||||||
messageVersionNumber = encodedAverageWind[0];
|
}
|
||||||
byteTime = Arrays.copyOfRange(encodedAverageWind, 1, 7);
|
|
||||||
byteRawPeriod = Arrays.copyOfRange(encodedAverageWind, 7, 9);
|
|
||||||
byteRawSpeed = Arrays.copyOfRange(encodedAverageWind, 9, 11);
|
|
||||||
bytePeriod2 = Arrays.copyOfRange(encodedAverageWind, 11, 13);
|
@Override
|
||||||
byteSpeed2 = Arrays.copyOfRange(encodedAverageWind, 13, 15);
|
public AC35Data decode(byte[] encodedMessage) throws InvalidMessageException {
|
||||||
bytePeriod3 = Arrays.copyOfRange(encodedAverageWind, 15, 17);
|
this.encodedMessage = encodedMessage;
|
||||||
byteSpeed3 = Arrays.copyOfRange(encodedAverageWind, 17, 19);
|
|
||||||
bytePeriod4 = Arrays.copyOfRange(encodedAverageWind, 19, 21);
|
try {
|
||||||
byteSpeed4 = Arrays.copyOfRange(encodedAverageWind, 21, 23);
|
|
||||||
|
byte messageVersionNumber = encodedMessage[0];
|
||||||
int msgNum = ByteConverter.bytesToInt(messageVersionNumber);
|
|
||||||
long lngTime = ByteConverter.bytesToLong(byteTime);
|
|
||||||
int intRawPeriod = ByteConverter.bytesToInt(byteRawPeriod);
|
byte[] byteTime = Arrays.copyOfRange(encodedMessage, 1, 7);
|
||||||
int intRawSpeed = ByteConverter.bytesToInt(byteRawSpeed);
|
long time = ByteConverter.bytesToLong(byteTime);
|
||||||
int intPeriod2 = ByteConverter.bytesToInt(bytePeriod2);
|
|
||||||
int intSpeed2 = ByteConverter.bytesToInt(byteSpeed2);
|
byte[] byteRawPeriod = Arrays.copyOfRange(encodedMessage, 7, 9);
|
||||||
int intPeriod3 = ByteConverter.bytesToInt(bytePeriod3);
|
int intRawPeriod = ByteConverter.bytesToInt(byteRawPeriod);
|
||||||
int intSpeed3 = ByteConverter.bytesToInt(byteSpeed3);
|
long rawPeriod = unpackAverageWindPeriod(intRawPeriod);
|
||||||
int intPeriod4 = ByteConverter.bytesToInt(bytePeriod4);
|
|
||||||
int intSpeed4 = ByteConverter.bytesToInt(byteSpeed4);
|
byte[] byteRawSpeed = Arrays.copyOfRange(encodedMessage, 9, 11);
|
||||||
|
int intRawSpeed = ByteConverter.bytesToInt(byteRawSpeed);
|
||||||
this.averageWind = new AverageWind(msgNum, lngTime, intRawPeriod, intRawSpeed, intPeriod2, intSpeed2, intPeriod3, intSpeed3, intPeriod4, intSpeed4);
|
double rawSpeedKnots = unpackMMperSecToKnots(intRawSpeed);
|
||||||
|
|
||||||
|
byte[] bytePeriod2 = Arrays.copyOfRange(encodedMessage, 11, 13);
|
||||||
|
int intPeriod2 = ByteConverter.bytesToInt(bytePeriod2);
|
||||||
|
long period2 = unpackAverageWindPeriod(intPeriod2);
|
||||||
|
|
||||||
|
byte[] byteSpeed2 = Arrays.copyOfRange(encodedMessage, 13, 15);
|
||||||
|
int intSpeed2 = ByteConverter.bytesToInt(byteSpeed2);
|
||||||
|
double speed2Knots = unpackMMperSecToKnots(intSpeed2);
|
||||||
|
|
||||||
|
byte[] bytePeriod3 = Arrays.copyOfRange(encodedMessage, 15, 17);
|
||||||
|
int intPeriod3 = ByteConverter.bytesToInt(bytePeriod3);
|
||||||
|
long period3 = unpackAverageWindPeriod(intPeriod3);
|
||||||
|
|
||||||
|
byte[] byteSpeed3 = Arrays.copyOfRange(encodedMessage, 17, 19);
|
||||||
|
int intSpeed3 = ByteConverter.bytesToInt(byteSpeed3);
|
||||||
|
double speed3Knots = unpackMMperSecToKnots(intSpeed3);
|
||||||
|
|
||||||
|
byte[] bytePeriod4 = Arrays.copyOfRange(encodedMessage, 19, 21);
|
||||||
|
int intPeriod4 = ByteConverter.bytesToInt(bytePeriod4);
|
||||||
|
long period4 = unpackAverageWindPeriod(intPeriod4);
|
||||||
|
|
||||||
|
byte[] byteSpeed4 = Arrays.copyOfRange(encodedMessage, 21, 23);
|
||||||
|
int intSpeed4 = ByteConverter.bytesToInt(byteSpeed4);
|
||||||
|
double speed4Knots = unpackMMperSecToKnots(intSpeed4);
|
||||||
|
|
||||||
|
|
||||||
|
message = new AverageWind(
|
||||||
|
messageVersionNumber,
|
||||||
|
time,
|
||||||
|
rawPeriod,
|
||||||
|
rawSpeedKnots,
|
||||||
|
period2,
|
||||||
|
speed2Knots,
|
||||||
|
period3,
|
||||||
|
speed3Knots,
|
||||||
|
period4,
|
||||||
|
speed4Knots);
|
||||||
|
|
||||||
|
return message;
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new InvalidMessageException("Could not decode AverageWind message.", e);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public AverageWind getAverageWind() {
|
/**
|
||||||
return averageWind;
|
* Returns the decoded message.
|
||||||
|
* @return The decoded message.
|
||||||
|
*/
|
||||||
|
public AverageWind getMessage() {
|
||||||
|
return message;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,18 +1,56 @@
|
|||||||
package network.MessageDecoders;
|
package network.MessageDecoders;
|
||||||
|
|
||||||
|
import network.Exceptions.InvalidMessageException;
|
||||||
|
import network.Messages.AC35Data;
|
||||||
|
import network.Messages.BoatAction;
|
||||||
import network.Messages.Enums.BoatActionEnum;
|
import network.Messages.Enums.BoatActionEnum;
|
||||||
|
|
||||||
public class BoatActionDecoder {
|
import java.util.Arrays;
|
||||||
byte byteBoatAction;
|
|
||||||
BoatActionEnum boatAction;
|
|
||||||
|
|
||||||
public BoatActionDecoder(byte[] encodedBoatAction) {
|
/**
|
||||||
byteBoatAction = encodedBoatAction[0];
|
* Decodes {@link BoatAction} messages.
|
||||||
|
*/
|
||||||
|
public class BoatActionDecoder implements MessageDecoder {
|
||||||
|
|
||||||
boatAction = BoatActionEnum.fromByte(byteBoatAction);
|
/**
|
||||||
|
* The encoded message.
|
||||||
|
*/
|
||||||
|
private byte[] encodedMessage;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The decoded message.
|
||||||
|
*/
|
||||||
|
private BoatAction message;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs a decoder to decode a given message.
|
||||||
|
*/
|
||||||
|
public BoatActionDecoder() {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public AC35Data decode(byte[] encodedMessage) throws InvalidMessageException {
|
||||||
|
this.encodedMessage = encodedMessage;
|
||||||
|
|
||||||
|
try {
|
||||||
|
BoatActionEnum boatActionEnum = BoatActionEnum.fromByte(encodedMessage[0]);
|
||||||
|
|
||||||
|
message = new BoatAction(boatActionEnum);
|
||||||
|
|
||||||
|
return message;
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new InvalidMessageException("Could not decode BoatAction message.", e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public BoatActionEnum getBoatAction() {
|
|
||||||
return boatAction;
|
/**
|
||||||
|
* Returns the decoded message.
|
||||||
|
* @return The decoded message.
|
||||||
|
*/
|
||||||
|
public BoatAction getMessage() {
|
||||||
|
return message;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -0,0 +1,98 @@
|
|||||||
|
package network.MessageDecoders;
|
||||||
|
|
||||||
|
|
||||||
|
import network.Exceptions.InvalidMessageException;
|
||||||
|
import network.Messages.BoatStatus;
|
||||||
|
import network.Messages.Enums.BoatStatusEnum;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import static network.Utils.ByteConverter.*;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Decodes {@link BoatStatus} messages.
|
||||||
|
*/
|
||||||
|
public class BoatStatusDecoder {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The encoded message.
|
||||||
|
*/
|
||||||
|
private byte[] encodedMessage;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The decoded message.
|
||||||
|
*/
|
||||||
|
private BoatStatus message;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs a decoder to decode a given message.
|
||||||
|
*/
|
||||||
|
public BoatStatusDecoder() {
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Decodes the contained message.
|
||||||
|
* @param encodedMessage The message to decode.
|
||||||
|
* @return The decoded message.
|
||||||
|
* @throws InvalidMessageException Thrown if the encoded message is invalid in some way, or cannot be decoded.
|
||||||
|
*/
|
||||||
|
public BoatStatus decode(byte[] encodedMessage) throws InvalidMessageException {
|
||||||
|
this.encodedMessage = encodedMessage;
|
||||||
|
|
||||||
|
try {
|
||||||
|
|
||||||
|
byte[] sourceIDBytes = Arrays.copyOfRange(encodedMessage, 0, 4);
|
||||||
|
int sourceID = bytesToInt(sourceIDBytes);
|
||||||
|
|
||||||
|
byte[] boatStatusBytes = Arrays.copyOfRange(encodedMessage, 4, 5);
|
||||||
|
BoatStatusEnum boatStatus = BoatStatusEnum.fromByte(boatStatusBytes[0]);
|
||||||
|
|
||||||
|
byte[] legNumberBytes = Arrays.copyOfRange(encodedMessage, 5, 6);
|
||||||
|
byte legNumber = legNumberBytes[0];
|
||||||
|
|
||||||
|
byte[] numPenaltiesAwardedBytes = Arrays.copyOfRange(encodedMessage, 6, 7);
|
||||||
|
byte numPenaltiesAwarded = numPenaltiesAwardedBytes[0];
|
||||||
|
|
||||||
|
byte[] numPenaltiesServedBytes = Arrays.copyOfRange(encodedMessage, 7, 8);
|
||||||
|
byte numPenaltiesServed = numPenaltiesServedBytes[0];
|
||||||
|
|
||||||
|
byte[] estTimeAtNextMarkBytes = Arrays.copyOfRange(encodedMessage, 8, 14);
|
||||||
|
long estTimeAtNextMark = bytesToLong(estTimeAtNextMarkBytes);
|
||||||
|
|
||||||
|
byte[] estTimeAtFinishBytes = Arrays.copyOfRange(encodedMessage, 14, 20);
|
||||||
|
long estTimeAtFinish = bytesToLong(estTimeAtFinishBytes);
|
||||||
|
|
||||||
|
message = new BoatStatus(
|
||||||
|
sourceID,
|
||||||
|
boatStatus,
|
||||||
|
legNumber,
|
||||||
|
numPenaltiesAwarded,
|
||||||
|
numPenaltiesServed,
|
||||||
|
estTimeAtNextMark,
|
||||||
|
estTimeAtFinish );
|
||||||
|
|
||||||
|
return message;
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new InvalidMessageException("Could not decode BoatStatus message.", e);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the decoded message.
|
||||||
|
* @return The decoded message.
|
||||||
|
*/
|
||||||
|
public BoatStatus getMessage() {
|
||||||
|
return message;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,64 +1,102 @@
|
|||||||
package network.MessageDecoders;
|
package network.MessageDecoders;
|
||||||
|
|
||||||
|
|
||||||
|
import network.Exceptions.InvalidMessageException;
|
||||||
import network.Messages.CourseWind;
|
import network.Messages.CourseWind;
|
||||||
|
import shared.model.Bearing;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
|
|
||||||
import static network.Utils.ByteConverter.bytesToInt;
|
import static network.Utils.AC35UnitConverter.*;
|
||||||
import static network.Utils.ByteConverter.bytesToLong;
|
import static network.Utils.ByteConverter.*;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Created by hba56 on 23/04/17.
|
* Decodes {@link CourseWind} messages.
|
||||||
*/
|
*/
|
||||||
public class CourseWindDecoder {
|
public class CourseWindDecoder {
|
||||||
byte messageVersionNumber;
|
|
||||||
byte byteWindID;
|
|
||||||
byte loopCount;
|
|
||||||
ArrayList<CourseWind> loopMessages = new ArrayList();
|
|
||||||
|
|
||||||
public CourseWindDecoder(byte[] encodedCourseWind) {
|
|
||||||
final int lengthInBytesOfMessages = 20;
|
|
||||||
|
|
||||||
messageVersionNumber = encodedCourseWind[0];
|
|
||||||
byteWindID = encodedCourseWind[1];
|
|
||||||
loopCount = encodedCourseWind[2];
|
|
||||||
byte[] loopMessagesBytes = Arrays.copyOfRange(encodedCourseWind, 3, lengthInBytesOfMessages*loopCount+3);
|
|
||||||
int messageLoopIndex = 0;
|
|
||||||
|
|
||||||
for (int i=0; i < loopCount; i++) {
|
|
||||||
byte[] messageBytes = Arrays.copyOfRange(loopMessagesBytes, messageLoopIndex, messageLoopIndex+20);
|
|
||||||
ArrayList test = new ArrayList();
|
|
||||||
byte[] windId = Arrays.copyOfRange(messageBytes, 0, 1);
|
|
||||||
byte[] time = Arrays.copyOfRange(messageBytes, 1, 7);
|
|
||||||
byte[] raceID = Arrays.copyOfRange(messageBytes, 7, 11);
|
|
||||||
byte[] windDirection = Arrays.copyOfRange(messageBytes, 11, 13);
|
|
||||||
byte[] windSpeed = Arrays.copyOfRange(messageBytes, 13, 15);
|
|
||||||
byte[] bestUpwindAngle = Arrays.copyOfRange(messageBytes, 15, 17);
|
|
||||||
byte[] bestDownwindAngle = Arrays.copyOfRange(messageBytes, 17, 19);
|
|
||||||
byte[] flags = Arrays.copyOfRange(messageBytes, 19, 20);
|
|
||||||
|
|
||||||
CourseWind message = new CourseWind(windId[0], bytesToLong(time),
|
|
||||||
bytesToInt(raceID), bytesToInt(windDirection),
|
|
||||||
bytesToInt(windSpeed), bytesToInt(bestUpwindAngle),
|
|
||||||
bytesToInt(bestDownwindAngle), flags[0]);
|
|
||||||
|
|
||||||
loopMessages.add(message);
|
|
||||||
messageLoopIndex += 20;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public ArrayList<CourseWind> getLoopMessages() {
|
/**
|
||||||
return loopMessages;
|
* The encoded message.
|
||||||
|
*/
|
||||||
|
private byte[] encodedMessage;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The decoded message.
|
||||||
|
*/
|
||||||
|
private CourseWind message;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs a decoder to decode a given message.
|
||||||
|
*/
|
||||||
|
public CourseWindDecoder() {
|
||||||
}
|
}
|
||||||
|
|
||||||
public byte getMessageVersionNumber() {
|
|
||||||
return messageVersionNumber;
|
/**
|
||||||
|
* Decodes the contained message.
|
||||||
|
* @param encodedMessage The message to decode.
|
||||||
|
* @return The decoded message.
|
||||||
|
* @throws InvalidMessageException Thrown if the encoded message is invalid in some way, or cannot be decoded.
|
||||||
|
*/
|
||||||
|
public CourseWind decode(byte[] encodedMessage) throws InvalidMessageException {
|
||||||
|
this.encodedMessage = encodedMessage;
|
||||||
|
|
||||||
|
try {
|
||||||
|
|
||||||
|
byte[] windId = Arrays.copyOfRange(encodedMessage, 0, 1);
|
||||||
|
|
||||||
|
byte[] timeBytes = Arrays.copyOfRange(encodedMessage, 1, 7);
|
||||||
|
long time = bytesToLong(timeBytes);
|
||||||
|
|
||||||
|
byte[] raceIDBytes = Arrays.copyOfRange(encodedMessage, 7, 11);
|
||||||
|
int raceIDInt = bytesToInt(raceIDBytes);
|
||||||
|
|
||||||
|
byte[] windDirectionBytes = Arrays.copyOfRange(encodedMessage, 11, 13);
|
||||||
|
int windDirectionInt = bytesToInt(windDirectionBytes);
|
||||||
|
Bearing windDirection = Bearing.fromDegrees(unpackHeading(windDirectionInt));
|
||||||
|
|
||||||
|
byte[] windSpeedBytes = Arrays.copyOfRange(encodedMessage, 13, 15);
|
||||||
|
int windSpeedInt = bytesToInt(windSpeedBytes);
|
||||||
|
double windSpeedKnots = unpackMMperSecToKnots(windSpeedInt);
|
||||||
|
|
||||||
|
byte[] bestUpwindAngleBytes = Arrays.copyOfRange(encodedMessage, 15, 17);
|
||||||
|
int bestUpwindAngleInt = bytesToInt(bestUpwindAngleBytes);
|
||||||
|
Bearing bestUpwindAngle = Bearing.fromDegrees(unpackHeading(bestUpwindAngleInt));
|
||||||
|
|
||||||
|
byte[] bestDownwindAngleBytes = Arrays.copyOfRange(encodedMessage, 17, 19);
|
||||||
|
int bestDownwindAngleInt = bytesToInt(bestDownwindAngleBytes);
|
||||||
|
Bearing bestDownwindAngle = Bearing.fromDegrees(unpackHeading(bestDownwindAngleInt));
|
||||||
|
|
||||||
|
byte[] flags = Arrays.copyOfRange(encodedMessage, 19, 20);
|
||||||
|
|
||||||
|
|
||||||
|
message = new CourseWind(
|
||||||
|
windId[0],
|
||||||
|
time,
|
||||||
|
raceIDInt,
|
||||||
|
windDirection,
|
||||||
|
windSpeedKnots,
|
||||||
|
bestUpwindAngle,
|
||||||
|
bestDownwindAngle,
|
||||||
|
flags[0]);
|
||||||
|
|
||||||
|
return message;
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new InvalidMessageException("Could not decode CourseWind message.", e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public byte getByteWindID() {
|
|
||||||
return byteWindID;
|
/**
|
||||||
|
* Returns the decoded message.
|
||||||
|
* @return The decoded message.
|
||||||
|
*/
|
||||||
|
public CourseWind getMessage() {
|
||||||
|
return message;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -0,0 +1,93 @@
|
|||||||
|
package network.MessageDecoders;
|
||||||
|
|
||||||
|
|
||||||
|
import network.Exceptions.InvalidMessageException;
|
||||||
|
import network.Messages.AC35Data;
|
||||||
|
import network.Messages.CourseWind;
|
||||||
|
import network.Messages.CourseWinds;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import static network.Utils.ByteConverter.bytesToInt;
|
||||||
|
import static network.Utils.ByteConverter.bytesToLong;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Decodes {@link CourseWinds} messages.
|
||||||
|
*/
|
||||||
|
public class CourseWindsDecoder implements MessageDecoder {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The encoded message.
|
||||||
|
*/
|
||||||
|
private byte[] encodedMessage;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The decoded message.
|
||||||
|
*/
|
||||||
|
private CourseWinds message;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs a decoder to decode a given message.
|
||||||
|
*/
|
||||||
|
public CourseWindsDecoder() {
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public AC35Data decode(byte[] encodedMessage) throws InvalidMessageException {
|
||||||
|
this.encodedMessage = encodedMessage;
|
||||||
|
|
||||||
|
try {
|
||||||
|
|
||||||
|
//The header is three bytes.
|
||||||
|
byte messageVersionNumber = encodedMessage[0];
|
||||||
|
byte byteWindID = encodedMessage[1];
|
||||||
|
byte loopCount = encodedMessage[2];
|
||||||
|
|
||||||
|
|
||||||
|
//A CourseWind object is 20 bytes.
|
||||||
|
final int courseWindByteLength = 20;
|
||||||
|
|
||||||
|
List<CourseWind> loopMessages = new ArrayList();
|
||||||
|
|
||||||
|
//The header is 3 bytes, so we need the remaining bytes.
|
||||||
|
byte[] loopMessagesBytes = Arrays.copyOfRange(encodedMessage, 3, courseWindByteLength * loopCount + 3);
|
||||||
|
|
||||||
|
for (int messageLoopIndex = 0; messageLoopIndex < (loopCount * courseWindByteLength); messageLoopIndex += courseWindByteLength) {
|
||||||
|
|
||||||
|
byte[] messageBytes = Arrays.copyOfRange(loopMessagesBytes, messageLoopIndex, messageLoopIndex + courseWindByteLength);
|
||||||
|
|
||||||
|
CourseWindDecoder courseWindDecoder = new CourseWindDecoder();
|
||||||
|
CourseWind courseWind = courseWindDecoder.decode(messageBytes);
|
||||||
|
|
||||||
|
loopMessages.add(courseWind);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
message = new CourseWinds(
|
||||||
|
messageVersionNumber,
|
||||||
|
byteWindID,
|
||||||
|
loopMessages);
|
||||||
|
|
||||||
|
return message;
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new InvalidMessageException("Could not decode CourseWinds message.", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the decoded message.
|
||||||
|
* @return The decoded message.
|
||||||
|
*/
|
||||||
|
public CourseWinds getMessage() {
|
||||||
|
return message;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@ -0,0 +1,70 @@
|
|||||||
|
package network.MessageDecoders;
|
||||||
|
|
||||||
|
|
||||||
|
import network.Exceptions.InvalidMessageTypeException;
|
||||||
|
import network.Messages.Enums.MessageType;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Factory to create the appropriate decoder for a given message.
|
||||||
|
*/
|
||||||
|
public class DecoderFactory {
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Private constructor. Currently doesn't need to be constructed.
|
||||||
|
*/
|
||||||
|
private DecoderFactory(){
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates the correct type of decoder for a given message type.
|
||||||
|
* @param type Type of message you want a decoder for.
|
||||||
|
* @return The decoder.
|
||||||
|
* @throws InvalidMessageTypeException If you pass in a {@link MessageType} that isn't recognised.
|
||||||
|
*/
|
||||||
|
public static MessageDecoder create(MessageType type) throws InvalidMessageTypeException {
|
||||||
|
|
||||||
|
|
||||||
|
switch (type) {
|
||||||
|
|
||||||
|
case HEARTBEAT: return new HeartBeatDecoder();
|
||||||
|
|
||||||
|
case RACESTATUS: return new RaceStatusDecoder();
|
||||||
|
|
||||||
|
//case DISPLAYTEXTMESSAGE: return new DisplayTextMessageDecoder();//TODO
|
||||||
|
|
||||||
|
case XMLMESSAGE: return new XMLMessageDecoder();
|
||||||
|
|
||||||
|
case RACESTARTSTATUS: return new RaceStartStatusDecoder();
|
||||||
|
|
||||||
|
//case YACHTEVENTCODE: return new YachtEventCodeDecoder();//TODO
|
||||||
|
|
||||||
|
//case YACHTACTIONCODE: return new YachtActionCodeDecoder();//TODO
|
||||||
|
|
||||||
|
//case CHATTERTEXT: return new ChatterTextDecoder();//TODO
|
||||||
|
|
||||||
|
case BOATLOCATION: return new BoatLocationDecoder();
|
||||||
|
|
||||||
|
case MARKROUNDING: return new MarkRoundingDecoder();
|
||||||
|
|
||||||
|
case COURSEWIND: return new CourseWindsDecoder();
|
||||||
|
|
||||||
|
case AVGWIND: return new AverageWindDecoder();
|
||||||
|
|
||||||
|
case REQUEST_TO_JOIN: return new RequestToJoinDecoder();
|
||||||
|
|
||||||
|
case JOIN_ACCEPTANCE: return new JoinAcceptanceDecoder();
|
||||||
|
|
||||||
|
case BOATACTION: return new BoatActionDecoder();
|
||||||
|
|
||||||
|
|
||||||
|
default: throw new InvalidMessageTypeException("Unrecognised message type: " + type);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@ -0,0 +1,57 @@
|
|||||||
|
package network.MessageDecoders;
|
||||||
|
|
||||||
|
import network.Exceptions.InvalidMessageException;
|
||||||
|
import network.Messages.AC35Data;
|
||||||
|
import network.Messages.Enums.BoatActionEnum;
|
||||||
|
import network.Messages.HeartBeat;
|
||||||
|
|
||||||
|
import static network.Utils.ByteConverter.bytesToLong;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Decodes {@link network.Messages.HeartBeat} messages.
|
||||||
|
*/
|
||||||
|
public class HeartBeatDecoder implements MessageDecoder {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The encoded message.
|
||||||
|
*/
|
||||||
|
private byte[] encodedMessage;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The decoded message.
|
||||||
|
*/
|
||||||
|
private HeartBeat message;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs a decoder to decode a given message.
|
||||||
|
*/
|
||||||
|
public HeartBeatDecoder() {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public AC35Data decode(byte[] encodedMessage) throws InvalidMessageException {
|
||||||
|
this.encodedMessage = encodedMessage;
|
||||||
|
|
||||||
|
try {
|
||||||
|
|
||||||
|
message = new HeartBeat(bytesToLong(encodedMessage));
|
||||||
|
|
||||||
|
return message;
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new InvalidMessageException("Could not decode HeartBeat message.", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the decoded message.
|
||||||
|
* @return The decoded message.
|
||||||
|
*/
|
||||||
|
public HeartBeat getMessage() {
|
||||||
|
return message;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@ -0,0 +1,74 @@
|
|||||||
|
package network.MessageDecoders;
|
||||||
|
|
||||||
|
|
||||||
|
import network.Exceptions.InvalidMessageException;
|
||||||
|
import network.Messages.AC35Data;
|
||||||
|
import network.Messages.Enums.JoinAcceptanceEnum;
|
||||||
|
import network.Messages.JoinAcceptance;
|
||||||
|
import network.Utils.ByteConverter;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Decoder for {@link JoinAcceptance} messages.
|
||||||
|
*/
|
||||||
|
public class JoinAcceptanceDecoder implements MessageDecoder {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The encoded message.
|
||||||
|
*/
|
||||||
|
private byte[] encodedMessage;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The decoded message.
|
||||||
|
*/
|
||||||
|
private JoinAcceptance message;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs a decoder to decode a given message.
|
||||||
|
*/
|
||||||
|
public JoinAcceptanceDecoder() {
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public AC35Data decode(byte[] encodedMessage) throws InvalidMessageException {
|
||||||
|
this.encodedMessage = encodedMessage;
|
||||||
|
|
||||||
|
try {
|
||||||
|
|
||||||
|
//SourceID is first four bytes.
|
||||||
|
byte[] sourceIdBytes = Arrays.copyOfRange(encodedMessage, 0, 4);
|
||||||
|
|
||||||
|
//Next byte is acceptance type.
|
||||||
|
byte[] acceptanceBytes = Arrays.copyOfRange(encodedMessage, 4, 5);
|
||||||
|
|
||||||
|
|
||||||
|
//SourceID is an int.
|
||||||
|
int sourceID = ByteConverter.bytesToInt(sourceIdBytes);
|
||||||
|
|
||||||
|
//Acceptance enum is a byte.
|
||||||
|
JoinAcceptanceEnum acceptanceType = JoinAcceptanceEnum.fromByte(acceptanceBytes[0]);
|
||||||
|
|
||||||
|
|
||||||
|
message = new JoinAcceptance(acceptanceType, sourceID);
|
||||||
|
|
||||||
|
return message;
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new InvalidMessageException("Could not decode JoinAcceptance message.", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the decoded message.
|
||||||
|
* @return The decoded message.
|
||||||
|
*/
|
||||||
|
public JoinAcceptance getMessage() {
|
||||||
|
return message;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
@ -1,52 +1,97 @@
|
|||||||
package network.MessageDecoders;
|
package network.MessageDecoders;
|
||||||
|
|
||||||
|
|
||||||
|
import network.Exceptions.InvalidMessageException;
|
||||||
|
import network.Messages.AC35Data;
|
||||||
|
import network.Messages.Enums.MarkRoundingBoatStatusEnum;
|
||||||
|
import network.Messages.Enums.MarkRoundingSideEnum;
|
||||||
|
import network.Messages.Enums.MarkRoundingTypeEnum;
|
||||||
import network.Messages.MarkRounding;
|
import network.Messages.MarkRounding;
|
||||||
import network.Utils.ByteConverter;
|
import network.Utils.ByteConverter;
|
||||||
|
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Created by hba56 on 23/04/17.
|
* Decoder for {@link MarkRounding} messages.
|
||||||
*/
|
*/
|
||||||
public class MarkRoundingDecoder {
|
public class MarkRoundingDecoder implements MessageDecoder {
|
||||||
byte messageVersionNumber;
|
|
||||||
byte[] byteTime;
|
/**
|
||||||
byte[] byteAck;
|
* The encoded message.
|
||||||
byte[] byteRaceID;
|
*/
|
||||||
byte[] byteSourceID;
|
private byte[] encodedMessage;
|
||||||
byte byteBoatStatus;
|
|
||||||
byte byteRoundingSide;
|
/**
|
||||||
byte byteMarkType;
|
* The decoded message.
|
||||||
byte byteMarkID;
|
*/
|
||||||
|
private MarkRounding message;
|
||||||
MarkRounding markRounding;
|
|
||||||
|
|
||||||
public MarkRoundingDecoder(byte[] encodedMarkRounding) {
|
/**
|
||||||
messageVersionNumber = encodedMarkRounding[0];
|
* Constructs a decoder to decode a given message.
|
||||||
byteTime = Arrays.copyOfRange(encodedMarkRounding, 1, 7);
|
*/
|
||||||
byteAck = Arrays.copyOfRange(encodedMarkRounding, 7, 9);
|
public MarkRoundingDecoder() {
|
||||||
byteRaceID = Arrays.copyOfRange(encodedMarkRounding, 9, 13);
|
|
||||||
byteSourceID = Arrays.copyOfRange(encodedMarkRounding, 13, 17);
|
|
||||||
byteBoatStatus = encodedMarkRounding[17];
|
|
||||||
byteRoundingSide = encodedMarkRounding[18];
|
|
||||||
byteMarkType = encodedMarkRounding[19];
|
|
||||||
byteMarkID = encodedMarkRounding[20];
|
|
||||||
|
|
||||||
int intMsgVer = ByteConverter.bytesToInt(messageVersionNumber);
|
|
||||||
long lngTime = ByteConverter.bytesToLong(byteTime);
|
|
||||||
int intAck = ByteConverter.bytesToInt(byteAck);
|
|
||||||
int intRaceID = ByteConverter.bytesToInt(byteRaceID);
|
|
||||||
int intSourceID = ByteConverter.bytesToInt(byteSourceID);
|
|
||||||
int intBoatState = ByteConverter.bytesToInt(byteBoatStatus);
|
|
||||||
int intRoundingSide = ByteConverter.bytesToInt(byteRoundingSide);
|
|
||||||
int intMarkType = ByteConverter.bytesToInt(byteMarkType);
|
|
||||||
int intMarkID = ByteConverter.bytesToInt(byteMarkID);
|
|
||||||
|
|
||||||
markRounding = new MarkRounding(intMsgVer, lngTime, intAck, intRaceID, intSourceID, intBoatState, intRoundingSide, intMarkType, intMarkID);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public MarkRounding getMarkRounding() {
|
@Override
|
||||||
return markRounding;
|
public AC35Data decode(byte[] encodedMessage) throws InvalidMessageException {
|
||||||
|
this.encodedMessage = encodedMessage;
|
||||||
|
|
||||||
|
try {
|
||||||
|
|
||||||
|
byte messageVersionNumber = encodedMessage[0];
|
||||||
|
|
||||||
|
byte[] byteTime = Arrays.copyOfRange(encodedMessage, 1, 7);
|
||||||
|
long time = ByteConverter.bytesToLong(byteTime);
|
||||||
|
|
||||||
|
byte[] byteAck = Arrays.copyOfRange(encodedMessage, 7, 9);
|
||||||
|
int ackNumber = ByteConverter.bytesToInt(byteAck);
|
||||||
|
|
||||||
|
byte[] byteRaceID = Arrays.copyOfRange(encodedMessage, 9, 13);
|
||||||
|
int raceID = ByteConverter.bytesToInt(byteRaceID);
|
||||||
|
|
||||||
|
byte[] byteSourceID = Arrays.copyOfRange(encodedMessage, 13, 17);
|
||||||
|
int sourceID = ByteConverter.bytesToInt(byteSourceID);
|
||||||
|
|
||||||
|
byte byteBoatStatus = encodedMessage[17];
|
||||||
|
MarkRoundingBoatStatusEnum boatStatus = MarkRoundingBoatStatusEnum.fromByte(byteBoatStatus);
|
||||||
|
|
||||||
|
byte byteRoundingSide = encodedMessage[18];
|
||||||
|
MarkRoundingSideEnum roundingSide = MarkRoundingSideEnum.fromByte(byteRoundingSide);
|
||||||
|
|
||||||
|
byte byteMarkType = encodedMessage[19];
|
||||||
|
MarkRoundingTypeEnum markType = MarkRoundingTypeEnum.fromByte(byteMarkType);
|
||||||
|
|
||||||
|
byte byteMarkID = encodedMessage[20];
|
||||||
|
|
||||||
|
|
||||||
|
message = new MarkRounding(
|
||||||
|
messageVersionNumber,
|
||||||
|
time,
|
||||||
|
ackNumber,
|
||||||
|
raceID,
|
||||||
|
sourceID,
|
||||||
|
boatStatus,
|
||||||
|
roundingSide,
|
||||||
|
markType,
|
||||||
|
byteMarkID);
|
||||||
|
|
||||||
|
|
||||||
|
return message;
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new InvalidMessageException("Could not decode AverageWind message.", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the decoded message.
|
||||||
|
*
|
||||||
|
* @return The decoded message.
|
||||||
|
*/
|
||||||
|
public MarkRounding getMessage() {
|
||||||
|
return message;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -0,0 +1,23 @@
|
|||||||
|
package network.MessageDecoders;
|
||||||
|
|
||||||
|
|
||||||
|
import network.Exceptions.InvalidMessageException;
|
||||||
|
import network.Messages.AC35Data;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This is the interface that all message decoders must implement.
|
||||||
|
* It allows for {@link #decode(byte[])}ing messages.
|
||||||
|
*/
|
||||||
|
public interface MessageDecoder {
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Decodes a given message.
|
||||||
|
* @param encodedMessage The message to decode.
|
||||||
|
* @return The decoded message.
|
||||||
|
* @throws InvalidMessageException Thrown if the encoded message is invalid in some way, or cannot be decoded.
|
||||||
|
*/
|
||||||
|
public AC35Data decode(byte[] encodedMessage) throws InvalidMessageException;
|
||||||
|
|
||||||
|
}
|
||||||
@ -1,66 +1,86 @@
|
|||||||
package network.MessageDecoders;
|
package network.MessageDecoders;
|
||||||
|
|
||||||
|
|
||||||
|
import network.Exceptions.InvalidMessageException;
|
||||||
|
import network.Messages.AC35Data;
|
||||||
|
import network.Messages.Enums.RaceStartTypeEnum;
|
||||||
|
import network.Messages.RaceStartStatus;
|
||||||
|
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
|
|
||||||
import static network.Utils.ByteConverter.*;
|
import static network.Utils.ByteConverter.*;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Created by hba56 on 21/04/17.
|
* Decodes {@link RaceStartStatus} messages.
|
||||||
*/
|
*/
|
||||||
public class RaceStartStatusDecoder {
|
public class RaceStartStatusDecoder implements MessageDecoder {
|
||||||
private byte messageVersion;
|
|
||||||
private byte[] timestamp;
|
|
||||||
private byte[] ackNumber;
|
|
||||||
private byte[] raceStartTime;
|
|
||||||
private byte[] raceIdentifier;
|
|
||||||
private byte notificationType;
|
|
||||||
|
|
||||||
private long time;
|
|
||||||
private short ack;
|
|
||||||
private long startTime;
|
|
||||||
private int raceID;
|
|
||||||
private char notification;
|
|
||||||
|
|
||||||
|
|
||||||
public RaceStartStatusDecoder(byte[] encodedRaceStartStatus) {
|
|
||||||
messageVersion = encodedRaceStartStatus[0];
|
|
||||||
timestamp = Arrays.copyOfRange(encodedRaceStartStatus, 1, 7);
|
|
||||||
ackNumber = Arrays.copyOfRange(encodedRaceStartStatus, 7, 9);
|
|
||||||
raceStartTime = Arrays.copyOfRange(encodedRaceStartStatus, 9, 15);
|
|
||||||
raceIdentifier = Arrays.copyOfRange(encodedRaceStartStatus, 15, 19);
|
|
||||||
notificationType = encodedRaceStartStatus[19];
|
|
||||||
|
|
||||||
time = bytesToLong(timestamp);
|
|
||||||
ack = bytesToShort(ackNumber);
|
|
||||||
startTime = bytesToLong(raceStartTime);
|
|
||||||
raceID = bytesToInt(raceIdentifier);
|
|
||||||
notification = bytesToChar(notificationType);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The encoded message.
|
||||||
|
*/
|
||||||
|
private byte[] encodedMessage;
|
||||||
|
|
||||||
public byte getMessageVersion() {
|
/**
|
||||||
return messageVersion;
|
* The decoded message.
|
||||||
}
|
*/
|
||||||
|
private RaceStartStatus message;
|
||||||
|
|
||||||
public long getTime() {
|
|
||||||
return time;
|
|
||||||
}
|
|
||||||
|
|
||||||
public short getAck() {
|
|
||||||
return ack;
|
|
||||||
}
|
|
||||||
|
|
||||||
public long getStartTime() {
|
/**
|
||||||
return startTime;
|
* Constructs a decoder to decode a given message.
|
||||||
|
*/
|
||||||
|
public RaceStartStatusDecoder() {
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getRaceID() {
|
|
||||||
return raceID;
|
@Override
|
||||||
|
public AC35Data decode(byte[] encodedMessage) throws InvalidMessageException {
|
||||||
|
this.encodedMessage = encodedMessage;
|
||||||
|
|
||||||
|
try {
|
||||||
|
|
||||||
|
byte messageVersion = encodedMessage[0];
|
||||||
|
|
||||||
|
byte[] timestamp = Arrays.copyOfRange(encodedMessage, 1, 7);
|
||||||
|
long time = bytesToLong(timestamp);
|
||||||
|
|
||||||
|
byte[] ackNumber = Arrays.copyOfRange(encodedMessage, 7, 9);
|
||||||
|
short ack = bytesToShort(ackNumber);
|
||||||
|
|
||||||
|
byte[] raceStartTime = Arrays.copyOfRange(encodedMessage, 9, 15);
|
||||||
|
long startTime = bytesToLong(raceStartTime);
|
||||||
|
|
||||||
|
byte[] raceIdentifier = Arrays.copyOfRange(encodedMessage, 15, 19);
|
||||||
|
int raceID = bytesToInt(raceIdentifier);
|
||||||
|
|
||||||
|
byte notificationType = encodedMessage[19];
|
||||||
|
|
||||||
|
|
||||||
|
message = new RaceStartStatus(
|
||||||
|
messageVersion,
|
||||||
|
time,
|
||||||
|
ack,
|
||||||
|
startTime,
|
||||||
|
raceID,
|
||||||
|
RaceStartTypeEnum.fromByte(notificationType)
|
||||||
|
);
|
||||||
|
|
||||||
|
return message;
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new InvalidMessageException("Could not decode RaceStartStatus message.", e);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public char getNotification() {
|
|
||||||
return notification;
|
/**
|
||||||
|
* Returns the decoded message.
|
||||||
|
* @return The decoded message.
|
||||||
|
*/
|
||||||
|
public RaceStartStatus getMessage() {
|
||||||
|
return message;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,119 +1,125 @@
|
|||||||
package network.MessageDecoders;
|
package network.MessageDecoders;
|
||||||
|
|
||||||
|
|
||||||
|
import network.Exceptions.InvalidMessageException;
|
||||||
|
import network.Messages.AC35Data;
|
||||||
import network.Messages.BoatStatus;
|
import network.Messages.BoatStatus;
|
||||||
|
import network.Messages.Enums.RaceStatusEnum;
|
||||||
|
import network.Messages.Enums.RaceTypeEnum;
|
||||||
|
import network.Messages.RaceStatus;
|
||||||
|
import network.Utils.AC35UnitConverter;
|
||||||
|
import shared.model.Bearing;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
import static network.Utils.ByteConverter.*;
|
import static network.Utils.ByteConverter.*;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Created by hba56 on 21/04/17.
|
* Decodes {@link RaceStatus} messages.
|
||||||
*/
|
*/
|
||||||
public class RaceStatusDecoder {
|
public class RaceStatusDecoder implements MessageDecoder {
|
||||||
private byte versionNum;
|
|
||||||
private byte[] timeBytes;
|
|
||||||
private byte[] raceID;
|
|
||||||
private byte raceStatus;
|
|
||||||
private byte[] expectedStart;
|
|
||||||
private byte[] raceWind;
|
|
||||||
private byte[] windSpeed;
|
|
||||||
private byte numBoats;
|
|
||||||
private byte bytesRaceType;
|
|
||||||
private byte[] boatsBytes;
|
|
||||||
|
|
||||||
private long time;
|
|
||||||
private int race;
|
|
||||||
private byte raceState;
|
|
||||||
private long startTime;
|
|
||||||
private int raceWindDir;
|
|
||||||
private short raceWindSpeed;
|
|
||||||
private int numberOfBoats;
|
|
||||||
private int raceType;
|
|
||||||
private ArrayList<BoatStatus> boats = new ArrayList<>();
|
|
||||||
|
|
||||||
|
|
||||||
public RaceStatusDecoder(byte[] encodedRaceStatus){
|
|
||||||
versionNum = encodedRaceStatus[0];
|
|
||||||
timeBytes = Arrays.copyOfRange(encodedRaceStatus, 1, 7);
|
|
||||||
raceID = Arrays.copyOfRange(encodedRaceStatus, 7, 11);
|
|
||||||
raceStatus = encodedRaceStatus[11];
|
|
||||||
expectedStart = Arrays.copyOfRange(encodedRaceStatus, 12, 18);
|
|
||||||
raceWind = Arrays.copyOfRange(encodedRaceStatus, 18, 20);
|
|
||||||
windSpeed = Arrays.copyOfRange(encodedRaceStatus, 20, 22);
|
|
||||||
numBoats = encodedRaceStatus[22];
|
|
||||||
bytesRaceType = encodedRaceStatus[23];
|
|
||||||
boatsBytes = Arrays.copyOfRange(encodedRaceStatus, 24, 25+20*this.numBoats);
|
|
||||||
|
|
||||||
time = bytesToLong(timeBytes);
|
|
||||||
race = bytesToInt(raceID);
|
|
||||||
raceState = raceStatus;
|
|
||||||
startTime = bytesToLong(expectedStart);
|
|
||||||
raceWindDir = bytesToInt(raceWind);
|
|
||||||
raceWindSpeed = bytesToShort(windSpeed);
|
|
||||||
numberOfBoats = bytesToInt(numBoats);
|
|
||||||
|
|
||||||
int boatLoopIndex = 0;
|
|
||||||
|
|
||||||
for (int i=0; i < numberOfBoats; i++) {
|
|
||||||
byte[] boatBytes = Arrays.copyOfRange(boatsBytes, boatLoopIndex, boatLoopIndex+20);
|
|
||||||
|
|
||||||
byte[] sourceID = Arrays.copyOfRange(boatBytes, 0, 3);
|
|
||||||
byte boatStatus = boatBytes[4];
|
|
||||||
byte legNumber = boatBytes[5];
|
|
||||||
byte numPenaltiesAwarded = boatBytes[6];
|
|
||||||
byte numPenaltiesServed = boatBytes[7];
|
|
||||||
byte[] estTimeAtNextMark = Arrays.copyOfRange(boatBytes, 8, 14);
|
|
||||||
byte[] estTimeAtFinish = Arrays.copyOfRange(boatBytes, 14, 20);
|
|
||||||
|
|
||||||
BoatStatus boat = new BoatStatus(bytesToInt(sourceID),boatStatus,
|
|
||||||
legNumber, numPenaltiesAwarded, numPenaltiesServed,
|
|
||||||
bytesToLong(estTimeAtNextMark), bytesToLong(estTimeAtFinish));
|
|
||||||
|
|
||||||
boats.add(boat);
|
|
||||||
boatLoopIndex += 20;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public byte getVersionNum() {
|
/**
|
||||||
return versionNum;
|
* The encoded message.
|
||||||
}
|
*/
|
||||||
|
private byte[] encodedMessage;
|
||||||
|
|
||||||
public long getTime() {
|
/**
|
||||||
return time;
|
* The decoded message.
|
||||||
}
|
*/
|
||||||
|
private RaceStatus message;
|
||||||
|
|
||||||
public int getRace() {
|
|
||||||
return race;
|
|
||||||
}
|
|
||||||
|
|
||||||
public byte getRaceState() {
|
|
||||||
return raceState;
|
|
||||||
}
|
|
||||||
|
|
||||||
public long getStartTime() {
|
/**
|
||||||
return startTime;
|
* Constructs a decoder to decode a given message.
|
||||||
|
*/
|
||||||
|
public RaceStatusDecoder() {
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getRaceWindDir() {
|
|
||||||
return raceWindDir;
|
|
||||||
}
|
|
||||||
|
|
||||||
public short getRaceWindSpeed() {
|
@Override
|
||||||
return raceWindSpeed;
|
public AC35Data decode(byte[] encodedMessage) throws InvalidMessageException {
|
||||||
}
|
this.encodedMessage = encodedMessage;
|
||||||
|
|
||||||
public int getNumberOfBoats() {
|
try {
|
||||||
return numberOfBoats;
|
|
||||||
}
|
byte[] versionNumBytes = Arrays.copyOfRange(encodedMessage, 0, 1);
|
||||||
|
byte versionNum = versionNumBytes[0];
|
||||||
|
|
||||||
|
byte[] timeBytes = Arrays.copyOfRange(encodedMessage, 1, 7);
|
||||||
|
long time = bytesToLong(timeBytes);
|
||||||
|
|
||||||
|
byte[] raceIDBytes = Arrays.copyOfRange(encodedMessage, 7, 11);
|
||||||
|
int raceID = bytesToInt(raceIDBytes);
|
||||||
|
|
||||||
|
byte[] raceStatusBytes = Arrays.copyOfRange(encodedMessage, 11, 12);
|
||||||
|
RaceStatusEnum raceStatus = RaceStatusEnum.fromByte(raceStatusBytes[0]);
|
||||||
|
|
||||||
|
byte[] expectedStartBytes = Arrays.copyOfRange(encodedMessage, 12, 18);
|
||||||
|
long expectedStart = bytesToLong(expectedStartBytes);
|
||||||
|
|
||||||
|
byte[] windDirectionBytes = Arrays.copyOfRange(encodedMessage, 18, 20);
|
||||||
|
int windDirectionInt = bytesToInt(windDirectionBytes);
|
||||||
|
Bearing windDirection = Bearing.fromDegrees(AC35UnitConverter.unpackHeading(windDirectionInt));
|
||||||
|
|
||||||
|
byte[] windSpeedBytes = Arrays.copyOfRange(encodedMessage, 20, 22);
|
||||||
|
int windSpeedInt = bytesToInt(windSpeedBytes);
|
||||||
|
double windSpeedKnots = AC35UnitConverter.unpackMMperSecToKnots(windSpeedInt);
|
||||||
|
|
||||||
public int getRaceType() {
|
byte[] numberOfBoatsBytes = Arrays.copyOfRange(encodedMessage, 22, 23);
|
||||||
return raceType;
|
int numberOfBoats = bytesToInt(numberOfBoatsBytes);
|
||||||
|
|
||||||
|
byte[] raceTypeBytes = Arrays.copyOfRange(encodedMessage, 23, 24);
|
||||||
|
RaceTypeEnum raceType = RaceTypeEnum.fromByte(raceTypeBytes[0]);
|
||||||
|
|
||||||
|
byte[] boatStatusesBytes = Arrays.copyOfRange(encodedMessage, 24, 25 + 20 * numberOfBoats);
|
||||||
|
List<BoatStatus> boatStatuses = new ArrayList<>();
|
||||||
|
|
||||||
|
|
||||||
|
//BoatStatus is 20 bytes.
|
||||||
|
int boatStatusByteLength = 20;
|
||||||
|
|
||||||
|
|
||||||
|
//Decode each BoatStatus.
|
||||||
|
for (int boatLoopIndex = 0; boatLoopIndex < (numberOfBoats * boatStatusByteLength); boatLoopIndex += boatStatusByteLength) {
|
||||||
|
|
||||||
|
byte[] boatStatusBytes = Arrays.copyOfRange(boatStatusesBytes, boatLoopIndex, boatLoopIndex + boatStatusByteLength);
|
||||||
|
|
||||||
|
BoatStatusDecoder boatStatusDecoder = new BoatStatusDecoder();
|
||||||
|
|
||||||
|
boatStatuses.add(boatStatusDecoder.decode(boatStatusBytes));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
message = new RaceStatus(
|
||||||
|
versionNum,
|
||||||
|
time,
|
||||||
|
raceID,
|
||||||
|
raceStatus,
|
||||||
|
expectedStart,
|
||||||
|
windDirection,
|
||||||
|
windSpeedKnots,
|
||||||
|
raceType,
|
||||||
|
boatStatuses);
|
||||||
|
|
||||||
|
return message;
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new InvalidMessageException("Could not decode RaceStatus message.", e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public ArrayList<BoatStatus> getBoats() {
|
|
||||||
return boats;
|
|
||||||
|
/**
|
||||||
|
* Returns the decoded message.
|
||||||
|
* @return The decoded message.
|
||||||
|
*/
|
||||||
|
public RaceStatus getMessage() {
|
||||||
|
return message;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -0,0 +1,67 @@
|
|||||||
|
package network.MessageDecoders;
|
||||||
|
|
||||||
|
|
||||||
|
import network.Exceptions.InvalidMessageException;
|
||||||
|
import network.Messages.AC35Data;
|
||||||
|
import network.Messages.Enums.RequestToJoinEnum;
|
||||||
|
import network.Messages.RequestToJoin;
|
||||||
|
import network.Utils.ByteConverter;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Decoder for {@link network.Messages.RequestToJoin} messages.
|
||||||
|
*/
|
||||||
|
public class RequestToJoinDecoder implements MessageDecoder{
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The encoded message.
|
||||||
|
*/
|
||||||
|
private byte[] encodedRequest;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The decoded message.
|
||||||
|
*/
|
||||||
|
private RequestToJoin message;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs a decoder to decode a given message.
|
||||||
|
*/
|
||||||
|
public RequestToJoinDecoder() {
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public AC35Data decode(byte[] encodedRequest) throws InvalidMessageException {
|
||||||
|
this.encodedRequest = encodedRequest;
|
||||||
|
|
||||||
|
try {
|
||||||
|
|
||||||
|
//Request type is first four bytes.
|
||||||
|
byte[] requestTypeBytes = Arrays.copyOfRange(encodedRequest, 0, 4);
|
||||||
|
|
||||||
|
//Request type is an integral type.
|
||||||
|
int requestTypeInt = ByteConverter.bytesToInt(requestTypeBytes);
|
||||||
|
RequestToJoinEnum requestType = RequestToJoinEnum.fromInt(requestTypeInt);
|
||||||
|
|
||||||
|
|
||||||
|
message = new RequestToJoin(requestType);
|
||||||
|
|
||||||
|
return message;
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new InvalidMessageException("Could not decode RequestToJoin message.", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the decoded message.
|
||||||
|
* @return The decoded message.
|
||||||
|
*/
|
||||||
|
public RequestToJoin getMessage() {
|
||||||
|
return message;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
@ -0,0 +1,92 @@
|
|||||||
|
package network.MessageEncoders;
|
||||||
|
|
||||||
|
|
||||||
|
import network.Exceptions.InvalidMessageException;
|
||||||
|
import network.Messages.AC35Data;
|
||||||
|
import network.Messages.AverageWind;
|
||||||
|
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
|
||||||
|
import static network.Utils.AC35UnitConverter.*;
|
||||||
|
import static network.Utils.ByteConverter.intToBytes;
|
||||||
|
import static network.Utils.ByteConverter.longToBytes;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This encoder can encode a {@link AverageWind} message.
|
||||||
|
*/
|
||||||
|
public class AverageWindEncoder implements MessageEncoder {
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor.
|
||||||
|
*/
|
||||||
|
public AverageWindEncoder() {
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public byte[] encode(AC35Data message) throws InvalidMessageException {
|
||||||
|
|
||||||
|
try {
|
||||||
|
|
||||||
|
//Downcast.
|
||||||
|
AverageWind averageWind = (AverageWind) message;
|
||||||
|
|
||||||
|
|
||||||
|
byte messageVersionNumber = averageWind.getMessageVersionNumber();
|
||||||
|
|
||||||
|
long time = averageWind.getTime();
|
||||||
|
byte[] byteTime = longToBytes(time, 6);
|
||||||
|
|
||||||
|
long rawPeriod = averageWind.getRawPeriod();
|
||||||
|
int rawPeriodInt = packAverageWindPeriod(rawPeriod);
|
||||||
|
byte[] byteRawPeriod = intToBytes(rawPeriodInt, 2);
|
||||||
|
|
||||||
|
double rawSampleSpeed = averageWind.getRawSpeedKnots();
|
||||||
|
int rawSampleSpeedInt = packKnotsToMMperSec(rawSampleSpeed);
|
||||||
|
byte[] byteRawSpeed = intToBytes(rawSampleSpeedInt, 2);
|
||||||
|
|
||||||
|
long period2 = averageWind.getSampleTwoPeriod();
|
||||||
|
int period2Int = packAverageWindPeriod(period2);
|
||||||
|
byte[] bytePeriod2 = intToBytes(period2Int, 2);
|
||||||
|
|
||||||
|
double speed2 = averageWind.getSampleTwoSpeedKnots();
|
||||||
|
int speed2Int = packKnotsToMMperSec(speed2);
|
||||||
|
byte[] byteSpeed2 = intToBytes(speed2Int, 2);
|
||||||
|
|
||||||
|
long period3 = averageWind.getSampleThreePeriod();
|
||||||
|
int period3Int = packAverageWindPeriod(period3);
|
||||||
|
byte[] bytePeriod3 = intToBytes(period3Int, 2);
|
||||||
|
|
||||||
|
double speed3 = averageWind.getSampleThreeSpeedKnots();
|
||||||
|
int speed3Int = packKnotsToMMperSec(speed3);
|
||||||
|
byte[] byteSpeed3 = intToBytes(speed3Int, 2);
|
||||||
|
|
||||||
|
long period4 = averageWind.getSampleFourPeriod();
|
||||||
|
int period4Int = packAverageWindPeriod(period4);
|
||||||
|
byte[] bytePeriod4 = intToBytes(period4Int, 2);
|
||||||
|
|
||||||
|
double speed4 = averageWind.getSampleFourSpeedKnots();
|
||||||
|
int speed4Int = packKnotsToMMperSec(speed4);
|
||||||
|
byte[] byteSpeed4 = intToBytes(speed4Int, 2);
|
||||||
|
|
||||||
|
|
||||||
|
ByteBuffer result = ByteBuffer.allocate(23);
|
||||||
|
result.put(messageVersionNumber);
|
||||||
|
result.put(byteTime);
|
||||||
|
result.put(byteRawPeriod);
|
||||||
|
result.put(byteRawSpeed);
|
||||||
|
result.put(bytePeriod2);
|
||||||
|
result.put(byteSpeed2);
|
||||||
|
result.put(bytePeriod3);
|
||||||
|
result.put(byteSpeed3);
|
||||||
|
result.put(bytePeriod4);
|
||||||
|
result.put(byteSpeed4);
|
||||||
|
return result.array();
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new InvalidMessageException("Could not encode AverageWind message.", e);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,47 @@
|
|||||||
|
package network.MessageEncoders;
|
||||||
|
|
||||||
|
|
||||||
|
import network.Exceptions.InvalidMessageException;
|
||||||
|
import network.Messages.AC35Data;
|
||||||
|
import network.Messages.BoatAction;
|
||||||
|
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
|
||||||
|
import static network.Utils.ByteConverter.intToBytes;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This encoder can encode a {@link BoatAction} message.
|
||||||
|
*/
|
||||||
|
public class BoatActionEncoder implements MessageEncoder {
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor.
|
||||||
|
*/
|
||||||
|
public BoatActionEncoder() {
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public byte[] encode(AC35Data message) throws InvalidMessageException {
|
||||||
|
|
||||||
|
try {
|
||||||
|
|
||||||
|
//Downcast.
|
||||||
|
BoatAction boatAction = (BoatAction) message;
|
||||||
|
|
||||||
|
//Message is 1 byte.
|
||||||
|
ByteBuffer boatActionMessage = ByteBuffer.allocate(1);
|
||||||
|
|
||||||
|
boatActionMessage.put(intToBytes(boatAction.getBoatAction().getValue(), 1));
|
||||||
|
|
||||||
|
byte[] result = boatActionMessage.array();
|
||||||
|
|
||||||
|
return result;
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new InvalidMessageException("Could not encode BoatAction message.", e);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,91 @@
|
|||||||
|
package network.MessageEncoders;
|
||||||
|
|
||||||
|
|
||||||
|
import network.Exceptions.InvalidMessageException;
|
||||||
|
import network.Messages.AC35Data;
|
||||||
|
import network.Messages.BoatLocation;
|
||||||
|
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
|
||||||
|
import static network.Utils.AC35UnitConverter.*;
|
||||||
|
import static network.Utils.ByteConverter.intToBytes;
|
||||||
|
import static network.Utils.ByteConverter.longToBytes;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This encoder can encode a {@link BoatLocation} message.
|
||||||
|
*/
|
||||||
|
public class BoatLocationEncoder implements MessageEncoder {
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor.
|
||||||
|
*/
|
||||||
|
public BoatLocationEncoder() {
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public byte[] encode(AC35Data message) throws InvalidMessageException {
|
||||||
|
|
||||||
|
try {
|
||||||
|
|
||||||
|
//Downcast.
|
||||||
|
BoatLocation boatLocation = (BoatLocation) message;
|
||||||
|
|
||||||
|
|
||||||
|
int messageVersionNumber = 0b1;
|
||||||
|
byte[] messageVersionBytes = intToBytes(messageVersionNumber, 1);
|
||||||
|
byte[] time = longToBytes(boatLocation.getTime(), 6);
|
||||||
|
byte[] sourceID = intToBytes(boatLocation.getSourceID(), 4);
|
||||||
|
byte[] seqNum = longToBytes(boatLocation.getSequenceNumber(), 4);
|
||||||
|
byte[] deviceType = intToBytes(boatLocation.getDeviceType().getValue(), 1);
|
||||||
|
byte[] latitude = intToBytes(packGPS(boatLocation.getLatitude()), 4);
|
||||||
|
byte[] longitude = intToBytes(packGPS(boatLocation.getLongitude()), 4);
|
||||||
|
byte[] altitude = intToBytes(boatLocation.getAltitude(), 4);
|
||||||
|
byte[] heading = intToBytes(packHeading(boatLocation.getHeading().degrees()), 2);
|
||||||
|
byte[] pitch = intToBytes(boatLocation.getPitch(), 2);
|
||||||
|
byte[] roll = intToBytes(boatLocation.getRoll(), 2);
|
||||||
|
byte[] boatSpeed = intToBytes(packKnotsToMMperSec(boatLocation.getBoatSpeedKnots()), 2);
|
||||||
|
byte[] cog = intToBytes(packHeading(boatLocation.getBoatCOG().degrees()), 2);
|
||||||
|
byte[] sog = intToBytes(packKnotsToMMperSec(boatLocation.getBoatSOGKnots()), 2);
|
||||||
|
byte[] apparentWindSpeed = intToBytes(packKnotsToMMperSec(boatLocation.getApparentWindSpeedKnots()), 2);
|
||||||
|
byte[] apparentWindAngle = intToBytes(packTrueWindAngle(boatLocation.getApparentWindAngle().degrees()), 2);
|
||||||
|
byte[] trueWindSpeed = intToBytes(packKnotsToMMperSec(boatLocation.getTrueWindSpeedKnots()), 2);
|
||||||
|
byte[] trueWindDirection = intToBytes(packHeading(boatLocation.getTrueWindDirection().degrees()), 2);
|
||||||
|
byte[] trueWindAngle = intToBytes(packTrueWindAngle(boatLocation.getTrueWindAngle().degrees()), 2);
|
||||||
|
byte[] currentDrift = intToBytes(packKnotsToMMperSec(boatLocation.getCurrentDriftKnots()), 2);
|
||||||
|
byte[] currentSet = intToBytes(packHeading(boatLocation.getCurrentSet().degrees()), 2);
|
||||||
|
byte[] rudderAngle = intToBytes(packTrueWindAngle(boatLocation.getRudderAngle().degrees()), 2);
|
||||||
|
|
||||||
|
ByteBuffer result = ByteBuffer.allocate(56);
|
||||||
|
result.put(messageVersionBytes);
|
||||||
|
result.put(time);
|
||||||
|
result.put(sourceID);
|
||||||
|
result.put(seqNum);
|
||||||
|
result.put(deviceType);
|
||||||
|
result.put(latitude);
|
||||||
|
result.put(longitude);
|
||||||
|
result.put(altitude);
|
||||||
|
result.put(heading);
|
||||||
|
result.put(pitch);
|
||||||
|
result.put(roll);
|
||||||
|
result.put(boatSpeed);
|
||||||
|
result.put(cog);
|
||||||
|
result.put(sog);
|
||||||
|
result.put(apparentWindSpeed);
|
||||||
|
result.put(apparentWindAngle);
|
||||||
|
result.put(trueWindSpeed);
|
||||||
|
result.put(trueWindDirection);
|
||||||
|
result.put(trueWindAngle);
|
||||||
|
result.put(currentDrift);
|
||||||
|
result.put(currentSet);
|
||||||
|
result.put(rudderAngle);
|
||||||
|
|
||||||
|
return result.array();
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new InvalidMessageException("Could not encode BoatLocation message.", e);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,65 @@
|
|||||||
|
package network.MessageEncoders;
|
||||||
|
|
||||||
|
|
||||||
|
import network.Exceptions.InvalidMessageException;
|
||||||
|
import network.Messages.BoatStatus;
|
||||||
|
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
|
||||||
|
import static network.Utils.ByteConverter.intToBytes;
|
||||||
|
import static network.Utils.ByteConverter.longToBytes;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This encoder can encode a {@link BoatStatus} message.
|
||||||
|
*/
|
||||||
|
public class BoatStatusEncoder {
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor.
|
||||||
|
*/
|
||||||
|
public BoatStatusEncoder() {
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Encodes a given BoatStatus message.
|
||||||
|
* @param message The message to encode.
|
||||||
|
* @return The encoded message.
|
||||||
|
* @throws InvalidMessageException Thrown if the message is invalid in some way, or cannot be encoded.
|
||||||
|
*/
|
||||||
|
public byte[] encode(BoatStatus message) throws InvalidMessageException {
|
||||||
|
|
||||||
|
try {
|
||||||
|
|
||||||
|
|
||||||
|
//Downcast.
|
||||||
|
BoatStatus boatStatus = (BoatStatus) message;
|
||||||
|
|
||||||
|
//BoatStatus is 20 bytes.
|
||||||
|
ByteBuffer boatStatusBuffer = ByteBuffer.allocate(20);
|
||||||
|
|
||||||
|
byte[] sourceID = intToBytes(boatStatus.getSourceID());
|
||||||
|
byte[] boatStatusBytes = intToBytes(boatStatus.getBoatStatus().getValue(), 1);
|
||||||
|
byte[] legNum = intToBytes(boatStatus.getLegNumber(), 1);
|
||||||
|
byte[] numPenalties = intToBytes(boatStatus.getNumPenaltiesAwarded(), 1);
|
||||||
|
byte[] numPenaltiesServed = intToBytes(boatStatus.getNumPenaltiesServed(), 1);
|
||||||
|
byte[] estNextMarkTime = longToBytes(boatStatus.getEstTimeAtNextMark(), 6);
|
||||||
|
byte[] estFinishTime = longToBytes(boatStatus.getEstTimeAtFinish(), 6);
|
||||||
|
|
||||||
|
boatStatusBuffer.put(sourceID);
|
||||||
|
boatStatusBuffer.put(boatStatusBytes);
|
||||||
|
boatStatusBuffer.put(legNum);
|
||||||
|
boatStatusBuffer.put(numPenalties);
|
||||||
|
boatStatusBuffer.put(numPenaltiesServed);
|
||||||
|
boatStatusBuffer.put(estNextMarkTime);
|
||||||
|
boatStatusBuffer.put(estFinishTime);
|
||||||
|
|
||||||
|
return boatStatusBuffer.array();
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new InvalidMessageException("Could not encode BoatStatus message.", e);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,81 @@
|
|||||||
|
package network.MessageEncoders;
|
||||||
|
|
||||||
|
|
||||||
|
import network.Exceptions.InvalidMessageException;
|
||||||
|
import network.Messages.CourseWind;
|
||||||
|
import shared.model.Bearing;
|
||||||
|
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
import java.util.Arrays;
|
||||||
|
|
||||||
|
import static network.Utils.AC35UnitConverter.*;
|
||||||
|
import static network.Utils.ByteConverter.*;
|
||||||
|
import static network.Utils.ByteConverter.bytesToInt;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This encoder can encode a {@link CourseWind} message.
|
||||||
|
*/
|
||||||
|
public class CourseWindEncoder {
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor.
|
||||||
|
*/
|
||||||
|
public CourseWindEncoder() {
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Encodes a given CourseWind message.
|
||||||
|
* @param message The message to encode.
|
||||||
|
* @return The encoded message.
|
||||||
|
* @throws InvalidMessageException Thrown if the message is invalid in some way, or cannot be encoded.
|
||||||
|
*/
|
||||||
|
public byte[] encode(CourseWind message) throws InvalidMessageException {
|
||||||
|
|
||||||
|
try {
|
||||||
|
|
||||||
|
CourseWind courseWind = message;
|
||||||
|
|
||||||
|
|
||||||
|
//CourseWind is 20 bytes.
|
||||||
|
ByteBuffer courseWindBuffer = ByteBuffer.allocate(20);
|
||||||
|
|
||||||
|
|
||||||
|
byte[] windId = intToBytes(courseWind.getID(), 1);
|
||||||
|
|
||||||
|
byte[] timeBytes = longToBytes(courseWind.getTime(), 6);
|
||||||
|
|
||||||
|
byte[] raceIDBytes = intToBytes(courseWind.getRaceID(), 4);
|
||||||
|
|
||||||
|
int windDirectionInt = packHeading(courseWind.getWindDirection().degrees());
|
||||||
|
byte[] windDirectionBytes = intToBytes(windDirectionInt, 2);
|
||||||
|
|
||||||
|
int windSpeedInt = packKnotsToMMperSec(courseWind.getWindSpeedKnots());
|
||||||
|
byte[] windSpeedBytes = intToBytes(windSpeedInt, 2);
|
||||||
|
|
||||||
|
int bestUpwindAngleInt = packHeading(courseWind.getBestUpwindAngle().degrees());
|
||||||
|
byte[] bestUpwindAngleBytes = intToBytes(bestUpwindAngleInt, 2);
|
||||||
|
|
||||||
|
int bestDownwindAngleInt = packHeading(courseWind.getBestDownwindAngle().degrees());
|
||||||
|
byte[] bestDownwindAngleBytes = intToBytes(bestDownwindAngleInt, 2);
|
||||||
|
|
||||||
|
byte[] flags = intToBytes(courseWind.getFlags(), 1);
|
||||||
|
|
||||||
|
courseWindBuffer.put(windId);
|
||||||
|
courseWindBuffer.put(timeBytes);
|
||||||
|
courseWindBuffer.put(raceIDBytes);
|
||||||
|
courseWindBuffer.put(windDirectionBytes);
|
||||||
|
courseWindBuffer.put(windSpeedBytes);
|
||||||
|
courseWindBuffer.put(bestUpwindAngleBytes);
|
||||||
|
courseWindBuffer.put(bestDownwindAngleBytes);
|
||||||
|
courseWindBuffer.put(flags);
|
||||||
|
|
||||||
|
return courseWindBuffer.array();
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new InvalidMessageException("Could not encode CourseWind message.", e);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,64 @@
|
|||||||
|
package network.MessageEncoders;
|
||||||
|
|
||||||
|
|
||||||
|
import network.Exceptions.InvalidMessageException;
|
||||||
|
import network.Messages.AC35Data;
|
||||||
|
import network.Messages.BoatAction;
|
||||||
|
import network.Messages.CourseWind;
|
||||||
|
import network.Messages.CourseWinds;
|
||||||
|
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
|
||||||
|
import static network.Utils.ByteConverter.intToBytes;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This encoder can encode a {@link CourseWinds} message.
|
||||||
|
*/
|
||||||
|
public class CourseWindsEncoder implements MessageEncoder {
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor.
|
||||||
|
*/
|
||||||
|
public CourseWindsEncoder() {
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public byte[] encode(AC35Data message) throws InvalidMessageException {
|
||||||
|
|
||||||
|
try {
|
||||||
|
|
||||||
|
//Downcast.
|
||||||
|
CourseWinds courseWinds = (CourseWinds) message;
|
||||||
|
|
||||||
|
|
||||||
|
byte messageVersionNumber = CourseWinds.currentMessageVersionNumber;
|
||||||
|
|
||||||
|
byte byteWindID = courseWinds.getSelectedWindID();
|
||||||
|
|
||||||
|
byte[] loopcount = intToBytes(courseWinds.getCourseWinds().size(), 1);
|
||||||
|
|
||||||
|
ByteBuffer result = ByteBuffer.allocate(3 + 20 * courseWinds.getCourseWinds().size());
|
||||||
|
|
||||||
|
result.put(messageVersionNumber);
|
||||||
|
result.put(byteWindID);
|
||||||
|
result.put(loopcount);
|
||||||
|
|
||||||
|
//Encode each CourseWind.
|
||||||
|
for (CourseWind wind : courseWinds.getCourseWinds()) {
|
||||||
|
|
||||||
|
CourseWindEncoder courseWindEncoder = new CourseWindEncoder();
|
||||||
|
byte[] encodedCourseWind = courseWindEncoder.encode(wind);
|
||||||
|
|
||||||
|
result.put(encodedCourseWind);
|
||||||
|
}
|
||||||
|
return result.array();
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new InvalidMessageException("Could not encode CourseWinds message.", e);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,70 @@
|
|||||||
|
package network.MessageEncoders;
|
||||||
|
|
||||||
|
|
||||||
|
import network.Exceptions.InvalidMessageTypeException;
|
||||||
|
import network.Messages.Enums.MessageType;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Factory to create the appropriate encoder for a given message.
|
||||||
|
*/
|
||||||
|
public class EncoderFactory {
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Private constructor. Currently doesn't need to be constructed.
|
||||||
|
*/
|
||||||
|
private EncoderFactory(){
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates the correct type of encoder for a given message type.
|
||||||
|
* @param type Type of message you want an encoder for.
|
||||||
|
* @return The encoder.
|
||||||
|
* @throws InvalidMessageTypeException If you pass in a {@link MessageType} that isn't recognised.
|
||||||
|
*/
|
||||||
|
public static MessageEncoder create(MessageType type) throws InvalidMessageTypeException {
|
||||||
|
|
||||||
|
|
||||||
|
switch (type) {
|
||||||
|
|
||||||
|
case HEARTBEAT: return new HeartBeatEncoder();
|
||||||
|
|
||||||
|
case RACESTATUS: return new RaceStatusEncoder();
|
||||||
|
|
||||||
|
//case DISPLAYTEXTMESSAGE: return new DisplayTextMessageEncoder();//TODO
|
||||||
|
|
||||||
|
case XMLMESSAGE: return new XMLMessageEncoder();
|
||||||
|
|
||||||
|
case RACESTARTSTATUS: return new RaceStartStatusEncoder();
|
||||||
|
|
||||||
|
//case YACHTEVENTCODE: return new YachtEventCodeEncoder();//TODO
|
||||||
|
|
||||||
|
//case YACHTACTIONCODE: return new YachtActionCodeEncoder();//TODO
|
||||||
|
|
||||||
|
//case CHATTERTEXT: return new ChatterTextEncoder();//TODO
|
||||||
|
|
||||||
|
case BOATLOCATION: return new BoatLocationEncoder();
|
||||||
|
|
||||||
|
case MARKROUNDING: return new MarkRoundingEncoder();
|
||||||
|
|
||||||
|
case COURSEWIND: return new CourseWindsEncoder();
|
||||||
|
|
||||||
|
case AVGWIND: return new AverageWindEncoder();
|
||||||
|
|
||||||
|
case REQUEST_TO_JOIN: return new RequestToJoinEncoder();
|
||||||
|
|
||||||
|
case JOIN_ACCEPTANCE: return new JoinAcceptanceEncoder();
|
||||||
|
|
||||||
|
case BOATACTION: return new BoatActionEncoder();
|
||||||
|
|
||||||
|
|
||||||
|
default: throw new InvalidMessageTypeException("Unrecognised message type: " + type);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@ -0,0 +1,46 @@
|
|||||||
|
package network.MessageEncoders;
|
||||||
|
|
||||||
|
|
||||||
|
import network.Exceptions.InvalidMessageException;
|
||||||
|
import network.Messages.AC35Data;
|
||||||
|
import network.Messages.HeartBeat;
|
||||||
|
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
|
||||||
|
import static network.Utils.ByteConverter.longToBytes;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This encoder can encode a {@link HeartBeat} message.
|
||||||
|
*/
|
||||||
|
public class HeartBeatEncoder implements MessageEncoder {
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor.
|
||||||
|
*/
|
||||||
|
public HeartBeatEncoder() {
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public byte[] encode(AC35Data message) throws InvalidMessageException {
|
||||||
|
|
||||||
|
try {
|
||||||
|
|
||||||
|
//Downcast.
|
||||||
|
HeartBeat heartbeat = (HeartBeat) message;
|
||||||
|
|
||||||
|
//Message is 4 bytes.
|
||||||
|
ByteBuffer heartBeat = ByteBuffer.allocate(4);
|
||||||
|
heartBeat.put(longToBytes(heartbeat.getSequenceNumber(), 4));
|
||||||
|
|
||||||
|
byte[] result = heartBeat.array();
|
||||||
|
|
||||||
|
return result;
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new InvalidMessageException("Could not encode HeartBeat message.", e);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,51 @@
|
|||||||
|
package network.MessageEncoders;
|
||||||
|
|
||||||
|
|
||||||
|
import network.Exceptions.InvalidMessageException;
|
||||||
|
import network.Messages.AC35Data;
|
||||||
|
import network.Messages.JoinAcceptance;
|
||||||
|
import network.Utils.ByteConverter;
|
||||||
|
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
|
||||||
|
import static network.Utils.ByteConverter.intToBytes;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This encoder can encode a {@link JoinAcceptance} message.
|
||||||
|
*/
|
||||||
|
public class JoinAcceptanceEncoder implements MessageEncoder {
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor.
|
||||||
|
*/
|
||||||
|
public JoinAcceptanceEncoder() {
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public byte[] encode(AC35Data message) throws InvalidMessageException {
|
||||||
|
|
||||||
|
try {
|
||||||
|
|
||||||
|
//Downcast.
|
||||||
|
JoinAcceptance joinAcceptance = (JoinAcceptance) message;
|
||||||
|
|
||||||
|
//Message is 5 bytes.
|
||||||
|
ByteBuffer joinAcceptanceBuffer = ByteBuffer.allocate(5);
|
||||||
|
|
||||||
|
//Source ID is first four bytes.
|
||||||
|
joinAcceptanceBuffer.put(intToBytes(joinAcceptance.getSourceID(), 4));
|
||||||
|
//Acceptance type is next byte.
|
||||||
|
joinAcceptanceBuffer.put(intToBytes(joinAcceptance.getAcceptanceType().getValue(), 1));
|
||||||
|
|
||||||
|
byte[] result = joinAcceptanceBuffer.array();
|
||||||
|
|
||||||
|
return result;
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new InvalidMessageException("Could not encode JoinAcceptance message.", e);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,64 @@
|
|||||||
|
package network.MessageEncoders;
|
||||||
|
|
||||||
|
|
||||||
|
import network.Exceptions.InvalidMessageException;
|
||||||
|
import network.Messages.AC35Data;
|
||||||
|
import network.Messages.MarkRounding;
|
||||||
|
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
|
||||||
|
import static network.Utils.ByteConverter.intToBytes;
|
||||||
|
import static network.Utils.ByteConverter.longToBytes;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This encoder can encode a {@link MarkRounding} message.
|
||||||
|
*/
|
||||||
|
public class MarkRoundingEncoder implements MessageEncoder {
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor.
|
||||||
|
*/
|
||||||
|
public MarkRoundingEncoder() {
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public byte[] encode(AC35Data message) throws InvalidMessageException {
|
||||||
|
|
||||||
|
try {
|
||||||
|
|
||||||
|
//Downcast.
|
||||||
|
MarkRounding markRounding = (MarkRounding) message;
|
||||||
|
|
||||||
|
byte messageVersionNumber = markRounding.getMessageVersionNumber();
|
||||||
|
byte[] byteTime = longToBytes(markRounding.getTime(), 6);
|
||||||
|
byte[] byteAck = intToBytes(markRounding.getAckNum(), 2);
|
||||||
|
byte[] byteRaceID = intToBytes(markRounding.getRaceID(), 4);
|
||||||
|
byte[] byteSourceID = intToBytes(markRounding.getSourceID(), 4);
|
||||||
|
byte[] byteBoatStatus = intToBytes(markRounding.getBoatStatus().getValue(), 1);
|
||||||
|
byte[] byteRoundingSide = intToBytes(markRounding.getRoundingSide().getValue(), 1);
|
||||||
|
byte[] byteMarkType = intToBytes(markRounding.getMarkType().getValue(), 1);
|
||||||
|
byte[] byteMarkID = intToBytes(markRounding.getMarkID(), 1);
|
||||||
|
|
||||||
|
|
||||||
|
ByteBuffer result = ByteBuffer.allocate(21);
|
||||||
|
|
||||||
|
result.put(messageVersionNumber);
|
||||||
|
result.put(byteTime);
|
||||||
|
result.put(byteAck);
|
||||||
|
result.put(byteRaceID);
|
||||||
|
result.put(byteSourceID);
|
||||||
|
result.put(byteBoatStatus);
|
||||||
|
result.put(byteRoundingSide);
|
||||||
|
result.put(byteMarkType);
|
||||||
|
result.put(byteMarkID);
|
||||||
|
|
||||||
|
return result.array();
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new InvalidMessageException("Could not encode MarkRounding message.", e);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,23 @@
|
|||||||
|
package network.MessageEncoders;
|
||||||
|
|
||||||
|
|
||||||
|
import network.Exceptions.InvalidMessageException;
|
||||||
|
import network.Messages.AC35Data;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This is the interface that all message encoders must implement.
|
||||||
|
* It allows for {@link #encode(AC35Data)}ing messages.
|
||||||
|
*/
|
||||||
|
public interface MessageEncoder {
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Encodes a given message.
|
||||||
|
* @param message The message to encode.
|
||||||
|
* @return Message in byte encoded form.
|
||||||
|
* @throws InvalidMessageException Thrown if the message is invalid in some way, or cannot be encoded.
|
||||||
|
*/
|
||||||
|
public byte[] encode(AC35Data message) throws InvalidMessageException;
|
||||||
|
|
||||||
|
}
|
||||||
@ -0,0 +1,58 @@
|
|||||||
|
package network.MessageEncoders;
|
||||||
|
|
||||||
|
|
||||||
|
import network.Exceptions.InvalidMessageException;
|
||||||
|
import network.Messages.AC35Data;
|
||||||
|
import network.Messages.RaceStartStatus;
|
||||||
|
import network.Utils.ByteConverter;
|
||||||
|
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
|
||||||
|
import static network.Utils.ByteConverter.intToBytes;
|
||||||
|
import static network.Utils.ByteConverter.longToBytes;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This encoder can encode a {@link RaceStartStatus} message.
|
||||||
|
*/
|
||||||
|
public class RaceStartStatusEncoder implements MessageEncoder {
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor.
|
||||||
|
*/
|
||||||
|
public RaceStartStatusEncoder() {
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public byte[] encode(AC35Data message) throws InvalidMessageException {
|
||||||
|
|
||||||
|
try {
|
||||||
|
|
||||||
|
//Downcast.
|
||||||
|
RaceStartStatus raceStartStatus = (RaceStartStatus) message;
|
||||||
|
|
||||||
|
|
||||||
|
byte messageVersion = raceStartStatus.getMessageVersionNumber();
|
||||||
|
byte[] timestamp = longToBytes(raceStartStatus.getTimestamp(), 6);
|
||||||
|
byte[] ackNumber = intToBytes(raceStartStatus.getAckNum(), 2);
|
||||||
|
byte[] raceStartTime = longToBytes(raceStartStatus.getRaceStartTime(), 6);
|
||||||
|
byte[] raceIdentifier = intToBytes(raceStartStatus.getRaceID());
|
||||||
|
byte[] notificationType = intToBytes(raceStartStatus.getNotificationType().getValue(), 1);
|
||||||
|
|
||||||
|
ByteBuffer result = ByteBuffer.allocate(20);
|
||||||
|
result.put(messageVersion);
|
||||||
|
result.put(timestamp);
|
||||||
|
result.put(ackNumber);
|
||||||
|
result.put(raceStartTime);
|
||||||
|
result.put(raceIdentifier);
|
||||||
|
result.put(notificationType);
|
||||||
|
|
||||||
|
return result.array();
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new InvalidMessageException("Could not encode RaceStartStatus message.", e);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,102 @@
|
|||||||
|
package network.MessageEncoders;
|
||||||
|
|
||||||
|
|
||||||
|
import network.Exceptions.InvalidMessageException;
|
||||||
|
import network.Messages.AC35Data;
|
||||||
|
import network.Messages.BoatStatus;
|
||||||
|
import network.Messages.RaceStatus;
|
||||||
|
import network.Utils.AC35UnitConverter;
|
||||||
|
import shared.model.Bearing;
|
||||||
|
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import static network.Utils.ByteConverter.bytesToInt;
|
||||||
|
import static network.Utils.ByteConverter.intToBytes;
|
||||||
|
import static network.Utils.ByteConverter.longToBytes;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This encoder can encode a {@link RaceStatus} message.
|
||||||
|
*/
|
||||||
|
public class RaceStatusEncoder implements MessageEncoder {
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor.
|
||||||
|
*/
|
||||||
|
public RaceStatusEncoder() {
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public byte[] encode(AC35Data message) throws InvalidMessageException {
|
||||||
|
|
||||||
|
try {
|
||||||
|
|
||||||
|
//Downcast.
|
||||||
|
RaceStatus raceStatus = (RaceStatus) message;
|
||||||
|
|
||||||
|
|
||||||
|
List<BoatStatus> boatStatuses = raceStatus.getBoatStatuses();
|
||||||
|
|
||||||
|
//24 byte header, plus 20 bytes per boat status.
|
||||||
|
ByteBuffer raceStatusMessage = ByteBuffer.allocate(24 + 20 * boatStatuses.size());
|
||||||
|
|
||||||
|
//Version Number 1 bytes. this changes with the pdf. (2)
|
||||||
|
byte versionNum = 0b10;
|
||||||
|
|
||||||
|
//time (6 bytes)
|
||||||
|
byte[] timeBytes = longToBytes(raceStatus.getCurrentTime(), 6);
|
||||||
|
|
||||||
|
//race identifier in case multiple races are going at once.
|
||||||
|
byte[] raceID = intToBytes(raceStatus.getRaceID());
|
||||||
|
|
||||||
|
//race status 0 - 10
|
||||||
|
byte[] raceStatusByte = intToBytes(raceStatus.getRaceStatus().getValue(), 1);
|
||||||
|
|
||||||
|
//number of milliseconds from Jan 1, 1970 for when the data is valid
|
||||||
|
byte[] expectedStart = longToBytes(raceStatus.getExpectedStartTime(), 6);
|
||||||
|
|
||||||
|
//North = 0x0000 East = 0x4000 South = 0x8000.
|
||||||
|
int windDirectionInt = AC35UnitConverter.packHeading(raceStatus.getWindDirection().degrees());
|
||||||
|
byte[] raceWind = intToBytes(windDirectionInt, 2);
|
||||||
|
|
||||||
|
//mm/sec
|
||||||
|
int windSpeedInt = AC35UnitConverter.packKnotsToMMperSec(raceStatus.getWindSpeed());
|
||||||
|
byte[] windSpeed = intToBytes(windSpeedInt, 2);
|
||||||
|
|
||||||
|
|
||||||
|
byte[] numBoats = intToBytes(boatStatuses.size(), 1);
|
||||||
|
|
||||||
|
//1 match race, 2 fleet race
|
||||||
|
byte[] bytesRaceType = intToBytes(raceStatus.getRaceType().getValue(), 1);
|
||||||
|
|
||||||
|
|
||||||
|
raceStatusMessage.put(versionNum);
|
||||||
|
raceStatusMessage.put(timeBytes);
|
||||||
|
raceStatusMessage.put(raceID);
|
||||||
|
raceStatusMessage.put(raceStatusByte);
|
||||||
|
raceStatusMessage.put(expectedStart);
|
||||||
|
raceStatusMessage.put(raceWind);
|
||||||
|
raceStatusMessage.put(windSpeed);
|
||||||
|
raceStatusMessage.put(numBoats);
|
||||||
|
raceStatusMessage.put(bytesRaceType);
|
||||||
|
|
||||||
|
//Encode each BoatStatus.
|
||||||
|
for (BoatStatus boatStatus : boatStatuses) {
|
||||||
|
|
||||||
|
BoatStatusEncoder boatStatusEncoder = new BoatStatusEncoder();
|
||||||
|
|
||||||
|
byte[] boatStatusEncoded = boatStatusEncoder.encode(boatStatus);
|
||||||
|
|
||||||
|
raceStatusMessage.put(boatStatusEncoded);
|
||||||
|
}
|
||||||
|
|
||||||
|
return raceStatusMessage.array();
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new InvalidMessageException("Could not encode RaceStatus message.", e);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,46 @@
|
|||||||
|
package network.MessageEncoders;
|
||||||
|
|
||||||
|
|
||||||
|
import network.Exceptions.InvalidMessageException;
|
||||||
|
import network.Messages.AC35Data;
|
||||||
|
import network.Messages.RequestToJoin;
|
||||||
|
import network.Utils.ByteConverter;
|
||||||
|
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This encoder can encode a {@link network.Messages.RequestToJoin} message.
|
||||||
|
*/
|
||||||
|
public class RequestToJoinEncoder implements MessageEncoder {
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor.
|
||||||
|
*/
|
||||||
|
public RequestToJoinEncoder() {
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public byte[] encode(AC35Data message) throws InvalidMessageException {
|
||||||
|
|
||||||
|
try {
|
||||||
|
|
||||||
|
//Downcast.
|
||||||
|
RequestToJoin requestToJoin = (RequestToJoin) message;
|
||||||
|
|
||||||
|
|
||||||
|
ByteBuffer requestToJoinBuffer = ByteBuffer.allocate(4);
|
||||||
|
|
||||||
|
requestToJoinBuffer.put(ByteConverter.intToBytes(requestToJoin.getRequestType().getValue(), 4));
|
||||||
|
|
||||||
|
byte[] result = requestToJoinBuffer.array();
|
||||||
|
|
||||||
|
return result;
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new InvalidMessageException("Could not encode RequestToJoin message.", e);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,69 @@
|
|||||||
|
package network.MessageEncoders;
|
||||||
|
|
||||||
|
|
||||||
|
import network.Exceptions.InvalidMessageException;
|
||||||
|
import network.Messages.AC35Data;
|
||||||
|
import network.Messages.XMLMessage;
|
||||||
|
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
|
||||||
|
import static network.Utils.ByteConverter.intToBytes;
|
||||||
|
import static network.Utils.ByteConverter.longToBytes;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This encoder can encode a {@link XMLMessage} message.
|
||||||
|
*/
|
||||||
|
public class XMLMessageEncoder implements MessageEncoder {
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor.
|
||||||
|
*/
|
||||||
|
public XMLMessageEncoder() {
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public byte[] encode(AC35Data message) throws InvalidMessageException {
|
||||||
|
|
||||||
|
try {
|
||||||
|
|
||||||
|
//Downcast.
|
||||||
|
XMLMessage xmlMessage = (XMLMessage) message;
|
||||||
|
|
||||||
|
|
||||||
|
byte[] messageBytes = xmlMessage.getXmlMessage().getBytes(StandardCharsets.UTF_8);
|
||||||
|
|
||||||
|
//Message is 14 + xmlMessage.length bytes.
|
||||||
|
ByteBuffer tempOutputByteBuffer = ByteBuffer.allocate(14 + messageBytes.length);
|
||||||
|
|
||||||
|
//ackNumber converted to bytes
|
||||||
|
byte[] ackNumberBytes = intToBytes(xmlMessage.getAckNumber(), 2);
|
||||||
|
|
||||||
|
//Timestamp converted to bytes.
|
||||||
|
byte[] timestampBytes = longToBytes(xmlMessage.getTimeStamp(), 6);
|
||||||
|
|
||||||
|
//sequenceNumber converted to bytes
|
||||||
|
byte[] sequenceNumberBytes = intToBytes(xmlMessage.getSequenceNumber(), 2);
|
||||||
|
|
||||||
|
//xmlMsgLength converted to bytes
|
||||||
|
byte[] xmlMsgLengthBytes = intToBytes(xmlMessage.getXmlMsgLength(), 2);
|
||||||
|
|
||||||
|
|
||||||
|
tempOutputByteBuffer.put(xmlMessage.getVersionNumber());
|
||||||
|
tempOutputByteBuffer.put(ackNumberBytes);
|
||||||
|
tempOutputByteBuffer.put(timestampBytes);
|
||||||
|
tempOutputByteBuffer.put(xmlMessage.getXmlMsgSubType().getValue());
|
||||||
|
tempOutputByteBuffer.put(sequenceNumberBytes);
|
||||||
|
tempOutputByteBuffer.put(xmlMsgLengthBytes);
|
||||||
|
tempOutputByteBuffer.put(messageBytes);
|
||||||
|
|
||||||
|
return tempOutputByteBuffer.array();
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new InvalidMessageException("Could not encode XMLMessage message.", e);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,131 @@
|
|||||||
|
package network.MessageRouters;
|
||||||
|
|
||||||
|
|
||||||
|
import network.Messages.AC35Data;
|
||||||
|
import network.Messages.Enums.MessageType;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
import shared.model.RunnableWithFramePeriod;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Optional;
|
||||||
|
import java.util.concurrent.BlockingQueue;
|
||||||
|
import java.util.logging.Level;
|
||||||
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This class routes {@link network.Messages.AC35Data} messages to an appropriate message controller.
|
||||||
|
*/
|
||||||
|
public class MessageRouter implements RunnableWithFramePeriod {
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Incoming queue of messages.
|
||||||
|
*/
|
||||||
|
private BlockingQueue<AC35Data> incomingMessages;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The routing map, which maps from a {@link MessageType} to a message queue.
|
||||||
|
*/
|
||||||
|
private Map<MessageType, BlockingQueue<AC35Data>> routeMap = new HashMap<>();
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The default routing queue.
|
||||||
|
* Messages without routes are sent here.
|
||||||
|
* Nothing by default, which means unrouted messages are discarded
|
||||||
|
*/
|
||||||
|
private Optional<BlockingQueue<AC35Data>> defaultRoute = Optional.empty();
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs a {@link MessageRouter} with a given incoming message queue.
|
||||||
|
* @param incomingMessages Incoming message queue to read from.
|
||||||
|
*/
|
||||||
|
public MessageRouter(BlockingQueue<AC35Data> incomingMessages) {
|
||||||
|
this.incomingMessages = incomingMessages;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the queue the message router reads from.
|
||||||
|
* Place messages onto this queue to pass them to the router.
|
||||||
|
* @return Queue the message router reads from.
|
||||||
|
*/
|
||||||
|
public BlockingQueue<AC35Data> getIncomingMessageQueue() {
|
||||||
|
return incomingMessages;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds a route, which routes a given type of message to a given queue.
|
||||||
|
* @param messageType The message type to route.
|
||||||
|
* @param queue The queue to route messages to.
|
||||||
|
*/
|
||||||
|
public void addRoute(MessageType messageType, BlockingQueue<AC35Data> queue) {
|
||||||
|
routeMap.put(messageType, queue);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes the route for a given {@link MessageType}.
|
||||||
|
* @param messageType MessageType to remove route for.
|
||||||
|
*/
|
||||||
|
public void removeRoute(MessageType messageType) {
|
||||||
|
routeMap.remove(messageType);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds a given queue as the default route for any unrouted message types.
|
||||||
|
* @param queue Queue to use as default route.
|
||||||
|
*/
|
||||||
|
public void addDefaultRoute(@NotNull BlockingQueue<AC35Data> queue) {
|
||||||
|
defaultRoute = Optional.of(queue);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes the current default route, if it exists.
|
||||||
|
*/
|
||||||
|
public void removeDefaultRoute() {
|
||||||
|
defaultRoute = Optional.empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
|
||||||
|
while (!Thread.interrupted()) {
|
||||||
|
|
||||||
|
try {
|
||||||
|
|
||||||
|
AC35Data message = incomingMessages.take();
|
||||||
|
|
||||||
|
|
||||||
|
BlockingQueue<AC35Data> queue = routeMap.get(message.getType());
|
||||||
|
|
||||||
|
if (queue != null) {
|
||||||
|
queue.put(message);
|
||||||
|
|
||||||
|
} else {
|
||||||
|
//No route. Use default.
|
||||||
|
BlockingQueue<AC35Data> defaultQueue = defaultRoute.orElse(null);
|
||||||
|
|
||||||
|
if (defaultQueue != null) {
|
||||||
|
defaultQueue.put(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
Logger.getGlobal().log(Level.SEVERE, "MessageRouter: " + this + " was interrupted on thread: " + Thread.currentThread() + " while reading message.", e);
|
||||||
|
Thread.currentThread().interrupt();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,38 @@
|
|||||||
|
package network.Messages;
|
||||||
|
|
||||||
|
import network.Messages.Enums.MessageType;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This is the message the client generates and sends to itself once the server has assigned a boat source ID with {@link JoinAcceptance}.
|
||||||
|
*/
|
||||||
|
public class AssignPlayerBoat extends AC35Data {
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The source ID of the boat assigned to the client.
|
||||||
|
* 0 indicates they haven't been assigned a boat.
|
||||||
|
*/
|
||||||
|
private int sourceID = 0;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs a AssignPlayerBoat message.
|
||||||
|
* @param sourceID The sourceID to assign to the client. 0 indicates no sourceID.
|
||||||
|
*/
|
||||||
|
public AssignPlayerBoat(int sourceID){
|
||||||
|
super(MessageType.ASSIGN_PLAYER_BOAT);
|
||||||
|
this.sourceID = sourceID;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the source ID of the boat assigned to the client.
|
||||||
|
* @return The source ID of the boat assigned to the client.
|
||||||
|
*/
|
||||||
|
public int getSourceID() {
|
||||||
|
return sourceID;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,107 @@
|
|||||||
|
package network.Messages.Enums;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Various device sources for a BoatLocation message.
|
||||||
|
*/
|
||||||
|
public enum BoatLocationDeviceEnum {
|
||||||
|
|
||||||
|
|
||||||
|
NOT_A_DEVICE(-1),
|
||||||
|
|
||||||
|
|
||||||
|
Unknown(0),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A yacht particpating in the race.
|
||||||
|
*/
|
||||||
|
RacingYacht(1),
|
||||||
|
|
||||||
|
CommitteeBoat(2),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A marker boat.
|
||||||
|
*/
|
||||||
|
Mark(3),
|
||||||
|
|
||||||
|
Pin(4),
|
||||||
|
|
||||||
|
ChaseBoat(5),
|
||||||
|
|
||||||
|
MedicalBoat(6),
|
||||||
|
|
||||||
|
MarshallBoat(7),
|
||||||
|
|
||||||
|
UmpireBoat(8),
|
||||||
|
|
||||||
|
UmpireSoftwareApplication(9),
|
||||||
|
|
||||||
|
PrincipalRaceOfficerApplication(10),
|
||||||
|
|
||||||
|
WeatherStation(11),
|
||||||
|
|
||||||
|
Helicopter(12),
|
||||||
|
|
||||||
|
DataProcessingApplication(13);
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Value of the enum.
|
||||||
|
*/
|
||||||
|
private byte value;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a BoatLocationDeviceEnum from a given primitive integer value, cast to a byte.
|
||||||
|
* @param value Integer, which is cast to byte, to construct from.
|
||||||
|
*/
|
||||||
|
private BoatLocationDeviceEnum(int value) {
|
||||||
|
this.value = (byte) value;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the primitive value of the enum.
|
||||||
|
* @return Primitive value of the enum.
|
||||||
|
*/
|
||||||
|
public byte getValue() {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stores a mapping between Byte values and BoatLocationDeviceEnum values.
|
||||||
|
*/
|
||||||
|
private static final Map<Byte, BoatLocationDeviceEnum> byteToDeviceMap = new HashMap<>();
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
Static initialization block. Initializes the byteToDeviceMap.
|
||||||
|
*/
|
||||||
|
static {
|
||||||
|
for (BoatLocationDeviceEnum type : BoatLocationDeviceEnum.values()) {
|
||||||
|
BoatLocationDeviceEnum.byteToDeviceMap.put(type.value, type);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the enumeration value which corresponds to a given byte value.
|
||||||
|
* @param deviceValue Byte value to convert to a BoatLocationDeviceEnum value.
|
||||||
|
* @return The BoatLocationDeviceEnum value which corresponds to the given byte value.
|
||||||
|
*/
|
||||||
|
public static BoatLocationDeviceEnum fromByte(byte deviceValue) {
|
||||||
|
//Gets the corresponding BoatLocationDeviceEnum from the map.
|
||||||
|
BoatLocationDeviceEnum type = BoatLocationDeviceEnum.byteToDeviceMap.get(deviceValue);
|
||||||
|
|
||||||
|
if (type == null) {
|
||||||
|
//If the byte value wasn't found, return the NOT_A_DEVICE BoatLocationDeviceEnum.
|
||||||
|
return BoatLocationDeviceEnum.NOT_A_DEVICE;
|
||||||
|
} else {
|
||||||
|
//Otherwise, return the BoatLocationDeviceEnum.
|
||||||
|
return type;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@ -0,0 +1,113 @@
|
|||||||
|
package network.Messages.Enums;
|
||||||
|
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This enum encapsulates the different ways in which a server may respond to a client {@link network.Messages.RequestToJoin} message.
|
||||||
|
*/
|
||||||
|
public enum JoinAcceptanceEnum {
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Client is allowed to join and spectate.
|
||||||
|
*/
|
||||||
|
JOIN_SUCCESSFUL_SPECTATOR(0),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Client is allowed to join and participate.
|
||||||
|
*/
|
||||||
|
JOIN_SUCCESSFUL_PARTICIPANT(1),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Client is allowed to join and play the tutorial.
|
||||||
|
*/
|
||||||
|
JOIN_SUCCESSFUL_TUTORIAL(2),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Client is allowed to join and participate as a ghost player.
|
||||||
|
*/
|
||||||
|
JOIN_SUCCESSFUL_GHOST(3),
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Join Request was denied.
|
||||||
|
*/
|
||||||
|
JOIN_FAILURE(0x10),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The server is completely full, cannot participate or spectate.
|
||||||
|
*/
|
||||||
|
SERVER_FULL(0x11),
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Used to indicate that a given byte value is invalid.
|
||||||
|
*/
|
||||||
|
NOT_AN_ACCEPTANCE_TYPE(-1);
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Primitive value of the enum.
|
||||||
|
*/
|
||||||
|
private byte value;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ctor. Creates a JoinAcceptanceEnum from a given primitive integer value, cast to a byte.
|
||||||
|
* @param value Integer, which is cast to byte, to construct from.
|
||||||
|
*/
|
||||||
|
private JoinAcceptanceEnum(int value) {
|
||||||
|
this.value = (byte) value;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the primitive value of the enum.
|
||||||
|
* @return Primitive value of the enum.
|
||||||
|
*/
|
||||||
|
public byte getValue() {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stores a mapping between Byte values and JoinAcceptanceEnum values.
|
||||||
|
*/
|
||||||
|
private static final Map<Byte, JoinAcceptanceEnum> byteToAcceptanceMap = new HashMap<>();
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
Static initialization block. Initializes the byteToAcceptanceMap.
|
||||||
|
*/
|
||||||
|
static {
|
||||||
|
for (JoinAcceptanceEnum type : JoinAcceptanceEnum.values()) {
|
||||||
|
JoinAcceptanceEnum.byteToAcceptanceMap.put(type.value, type);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the enumeration value which corresponds to a given byte value.
|
||||||
|
* @param joinAcceptanceEnum Byte value to convert to a JoinAcceptanceEnum value.
|
||||||
|
* @return The RequestToJoinEnum value which corresponds to the given byte value.
|
||||||
|
*/
|
||||||
|
public static JoinAcceptanceEnum fromByte(byte joinAcceptanceEnum) {
|
||||||
|
//Gets the corresponding MessageType from the map.
|
||||||
|
JoinAcceptanceEnum type = JoinAcceptanceEnum.byteToAcceptanceMap.get(joinAcceptanceEnum);
|
||||||
|
|
||||||
|
if (type == null) {
|
||||||
|
//If the byte value wasn't found, return the NOT_AN_ACCEPTANCE_TYPE JoinAcceptanceEnum.
|
||||||
|
return JoinAcceptanceEnum.NOT_AN_ACCEPTANCE_TYPE;
|
||||||
|
} else {
|
||||||
|
//Otherwise, return the JoinAcceptanceEnum.
|
||||||
|
return type;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
@ -0,0 +1,88 @@
|
|||||||
|
package network.Messages.Enums;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enumeration that encapsulates the various statuses a boat can have when rounding a mark.
|
||||||
|
*/
|
||||||
|
public enum MarkRoundingBoatStatusEnum {
|
||||||
|
|
||||||
|
UNKNOWN(0),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The boat is actively racing.
|
||||||
|
*/
|
||||||
|
RACING(1),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The boat has been disqualified.
|
||||||
|
*/
|
||||||
|
DSQ(2),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The boat has withdrawn from the race.
|
||||||
|
*/
|
||||||
|
WITHDRAWN(3),
|
||||||
|
|
||||||
|
NOT_A_STATUS(-1);
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Primitive value of the enum.
|
||||||
|
*/
|
||||||
|
private byte value;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ctor. Creates a {@link MarkRoundingBoatStatusEnum} from a given primitive integer value, cast to a byte.
|
||||||
|
* @param value Integer, which is cast to byte, to construct from.
|
||||||
|
*/
|
||||||
|
private MarkRoundingBoatStatusEnum(int value) {
|
||||||
|
this.value = (byte)value;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the primitive value of the enum.
|
||||||
|
* @return Primitive value of the enum.
|
||||||
|
*/
|
||||||
|
public byte getValue() {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
///Stores a mapping between Byte values and MarkRoundingBoatStatusEnum values.
|
||||||
|
private static final Map<Byte, MarkRoundingBoatStatusEnum> byteToStatusMap = new HashMap<>();
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
Static initialization block. Initializes the byteToStatusMap.
|
||||||
|
*/
|
||||||
|
static {
|
||||||
|
for (MarkRoundingBoatStatusEnum type : MarkRoundingBoatStatusEnum.values()) {
|
||||||
|
byteToStatusMap.put(type.value, type);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the enumeration value which corresponds to a given byte value.
|
||||||
|
* @param boatStatusByte Byte value to convert to a {@link MarkRoundingBoatStatusEnum} value.
|
||||||
|
* @return The {@link MarkRoundingBoatStatusEnum} value which corresponds to the given byte value.
|
||||||
|
*/
|
||||||
|
public static MarkRoundingBoatStatusEnum fromByte(byte boatStatusByte) {
|
||||||
|
//Gets the corresponding MarkRoundingBoatStatusEnum from the map.
|
||||||
|
MarkRoundingBoatStatusEnum type = byteToStatusMap.get(boatStatusByte);
|
||||||
|
|
||||||
|
if (type == null) {
|
||||||
|
//If the byte value wasn't found, return the NOT_A_STATUS MarkRoundingBoatStatusEnum.
|
||||||
|
return MarkRoundingBoatStatusEnum.NOT_A_STATUS;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
//Otherwise, return the MarkRoundingBoatStatusEnum.
|
||||||
|
return type;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@ -0,0 +1,88 @@
|
|||||||
|
package network.Messages.Enums;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enumeration that encapsulates the various mark identities.
|
||||||
|
*/
|
||||||
|
public enum MarkRoundingIDEnum {
|
||||||
|
|
||||||
|
UNKNOWN(0),
|
||||||
|
|
||||||
|
|
||||||
|
ENTRY_LIMIT_LINE(100),
|
||||||
|
|
||||||
|
ENTRY_LINE(101),
|
||||||
|
|
||||||
|
START_LINE(102),
|
||||||
|
|
||||||
|
FINISH_LINE(103),
|
||||||
|
|
||||||
|
SPEED_TEST_START(104),
|
||||||
|
|
||||||
|
SPEED_TEST_FINISH(105),
|
||||||
|
|
||||||
|
CLEAR_START(106),
|
||||||
|
|
||||||
|
NOT_AN_ID(-1);
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Primitive value of the enum.
|
||||||
|
*/
|
||||||
|
private byte value;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ctor. Creates a {@link MarkRoundingIDEnum} from a given primitive integer value, cast to a byte.
|
||||||
|
* @param value Integer, which is cast to byte, to construct from.
|
||||||
|
*/
|
||||||
|
private MarkRoundingIDEnum(int value) {
|
||||||
|
this.value = (byte)value;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the primitive value of the enum.
|
||||||
|
* @return Primitive value of the enum.
|
||||||
|
*/
|
||||||
|
public byte getValue() {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
///Stores a mapping between Byte values and MarkRoundingIDEnum values.
|
||||||
|
private static final Map<Byte, MarkRoundingIDEnum> byteToIDMap = new HashMap<>();
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
Static initialization block. Initializes the byteToIDMap.
|
||||||
|
*/
|
||||||
|
static {
|
||||||
|
for (MarkRoundingIDEnum type : MarkRoundingIDEnum.values()) {
|
||||||
|
byteToIDMap.put(type.value, type);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the enumeration value which corresponds to a given byte value.
|
||||||
|
* @param sideByte Byte value to convert to a {@link MarkRoundingIDEnum} value.
|
||||||
|
* @return The {@link MarkRoundingIDEnum} value which corresponds to the given byte value.
|
||||||
|
*/
|
||||||
|
public static MarkRoundingIDEnum fromByte(byte sideByte) {
|
||||||
|
//Gets the corresponding MarkRoundingIDEnum from the map.
|
||||||
|
MarkRoundingIDEnum type = byteToIDMap.get(sideByte);
|
||||||
|
|
||||||
|
if (type == null) {
|
||||||
|
//If the byte value wasn't found, return the NOT_AN_ID MarkRoundingIDEnum.
|
||||||
|
return MarkRoundingIDEnum.NOT_AN_ID;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
//Otherwise, return the MarkRoundingIDEnum.
|
||||||
|
return type;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@ -0,0 +1,82 @@
|
|||||||
|
package network.Messages.Enums;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enumeration that encapsulates the various sides around which a boat may pass a mark.
|
||||||
|
*/
|
||||||
|
public enum MarkRoundingSideEnum {
|
||||||
|
|
||||||
|
UNKNOWN(0),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Boat rounded around port side of mark.
|
||||||
|
*/
|
||||||
|
PORT(1),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Boat rounded around starboard side of mark.
|
||||||
|
*/
|
||||||
|
STARBOARD(2),
|
||||||
|
|
||||||
|
NOT_A_SIDE(-1);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Primitive value of the enum.
|
||||||
|
*/
|
||||||
|
private byte value;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ctor. Creates a {@link MarkRoundingSideEnum} from a given primitive integer value, cast to a byte.
|
||||||
|
* @param value Integer, which is cast to byte, to construct from.
|
||||||
|
*/
|
||||||
|
private MarkRoundingSideEnum(int value) {
|
||||||
|
this.value = (byte)value;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the primitive value of the enum.
|
||||||
|
* @return Primitive value of the enum.
|
||||||
|
*/
|
||||||
|
public byte getValue() {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
///Stores a mapping between Byte values and MarkRoundingSideEnum values.
|
||||||
|
private static final Map<Byte, MarkRoundingSideEnum> byteToSideMap = new HashMap<>();
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
Static initialization block. Initializes the byteToSideMap.
|
||||||
|
*/
|
||||||
|
static {
|
||||||
|
for (MarkRoundingSideEnum type : MarkRoundingSideEnum.values()) {
|
||||||
|
byteToSideMap.put(type.value, type);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the enumeration value which corresponds to a given byte value.
|
||||||
|
* @param sideByte Byte value to convert to a {@link MarkRoundingSideEnum} value.
|
||||||
|
* @return The {@link MarkRoundingSideEnum} value which corresponds to the given byte value.
|
||||||
|
*/
|
||||||
|
public static MarkRoundingSideEnum fromByte(byte sideByte) {
|
||||||
|
//Gets the corresponding MarkRoundingSideEnum from the map.
|
||||||
|
MarkRoundingSideEnum type = byteToSideMap.get(sideByte);
|
||||||
|
|
||||||
|
if (type == null) {
|
||||||
|
//If the byte value wasn't found, return the NOT_A_SIDE MarkRoundingSideEnum.
|
||||||
|
return MarkRoundingSideEnum.NOT_A_SIDE;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
//Otherwise, return the MarkRoundingSideEnum.
|
||||||
|
return type;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@ -0,0 +1,82 @@
|
|||||||
|
package network.Messages.Enums;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enumeration that encapsulates the various types of marks that may be passed.
|
||||||
|
*/
|
||||||
|
public enum MarkRoundingTypeEnum {
|
||||||
|
|
||||||
|
UNKNOWN(0),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The mark is a singular mark.
|
||||||
|
*/
|
||||||
|
MARK(1),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The mark is a gate (windward, leeward, start, finish, etc...).
|
||||||
|
*/
|
||||||
|
GATE(2),
|
||||||
|
|
||||||
|
NOT_A_TYPE(-1);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Primitive value of the enum.
|
||||||
|
*/
|
||||||
|
private byte value;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ctor. Creates a {@link MarkRoundingTypeEnum} from a given primitive integer value, cast to a byte.
|
||||||
|
* @param value Integer, which is cast to byte, to construct from.
|
||||||
|
*/
|
||||||
|
private MarkRoundingTypeEnum(int value) {
|
||||||
|
this.value = (byte)value;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the primitive value of the enum.
|
||||||
|
* @return Primitive value of the enum.
|
||||||
|
*/
|
||||||
|
public byte getValue() {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
///Stores a mapping between Byte values and MarkRoundingTypeEnum values.
|
||||||
|
private static final Map<Byte, MarkRoundingTypeEnum> byteToTypeMap = new HashMap<>();
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
Static initialization block. Initializes the byteToTypeMap.
|
||||||
|
*/
|
||||||
|
static {
|
||||||
|
for (MarkRoundingTypeEnum type : MarkRoundingTypeEnum.values()) {
|
||||||
|
byteToTypeMap.put(type.value, type);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the enumeration value which corresponds to a given byte value.
|
||||||
|
* @param sideByte Byte value to convert to a {@link MarkRoundingTypeEnum} value.
|
||||||
|
* @return The {@link MarkRoundingTypeEnum} value which corresponds to the given byte value.
|
||||||
|
*/
|
||||||
|
public static MarkRoundingTypeEnum fromByte(byte sideByte) {
|
||||||
|
//Gets the corresponding MarkRoundingTypeEnum from the map.
|
||||||
|
MarkRoundingTypeEnum type = byteToTypeMap.get(sideByte);
|
||||||
|
|
||||||
|
if (type == null) {
|
||||||
|
//If the byte value wasn't found, return the NOT_A_TYPE MarkRoundingTypeEnum.
|
||||||
|
return MarkRoundingTypeEnum.NOT_A_TYPE;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
//Otherwise, return the MarkRoundingTypeEnum.
|
||||||
|
return type;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@ -0,0 +1,97 @@
|
|||||||
|
package network.Messages.Enums;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enumeration that encapsulates the various types race start status notifications. See AC35 streaming spec, 4.5.
|
||||||
|
*/
|
||||||
|
public enum RaceStartTypeEnum {
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The race start time is being set.
|
||||||
|
*/
|
||||||
|
SET_RACE_START(1),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The race has been postponed.
|
||||||
|
*/
|
||||||
|
RACE_POSTPONED(2),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The race has been abandoned.
|
||||||
|
*/
|
||||||
|
RACE_ABANDONED(3),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The race has been terminated.
|
||||||
|
*/
|
||||||
|
RACE_TERMINATED(4),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Used to indicate that a given byte value is invalid.
|
||||||
|
*/
|
||||||
|
NOT_A_TYPE(-1);
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Primitive value of the enum.
|
||||||
|
*/
|
||||||
|
private byte value;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ctor. Creates a RaceStartTypeEnum from a given primitive integer value, cast to a byte.
|
||||||
|
* @param value Integer, which is cast to byte, to construct from.
|
||||||
|
*/
|
||||||
|
private RaceStartTypeEnum(int value) {
|
||||||
|
this.value = (byte) value;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the primitive value of the enum.
|
||||||
|
* @return Primitive value of the enum.
|
||||||
|
*/
|
||||||
|
public byte getValue() {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stores a mapping between Byte values and RaceStartTypeEnum values.
|
||||||
|
*/
|
||||||
|
private static final Map<Byte, RaceStartTypeEnum> byteToTypeMap = new HashMap<>();
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
Static initialization block. Initializes the byteToTypeMap.
|
||||||
|
*/
|
||||||
|
static {
|
||||||
|
for (RaceStartTypeEnum type : RaceStartTypeEnum.values()) {
|
||||||
|
RaceStartTypeEnum.byteToTypeMap.put(type.value, type);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the enumeration value which corresponds to a given byte value.
|
||||||
|
* @param startTypeEnum Byte value to convert to a RaceStartTypeEnum value.
|
||||||
|
* @return The RaceStartTypeEnum value which corresponds to the given byte value.
|
||||||
|
*/
|
||||||
|
public static RaceStartTypeEnum fromByte(byte startTypeEnum) {
|
||||||
|
//Gets the corresponding MessageType from the map.
|
||||||
|
RaceStartTypeEnum type = RaceStartTypeEnum.byteToTypeMap.get(startTypeEnum);
|
||||||
|
|
||||||
|
if (type == null) {
|
||||||
|
//If the byte value wasn't found, return the NOT_A_TYPE RaceStartTypeEnum.
|
||||||
|
return RaceStartTypeEnum.NOT_A_TYPE;
|
||||||
|
} else {
|
||||||
|
//Otherwise, return the RaceStartTypeEnum.
|
||||||
|
return type;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
@ -0,0 +1,102 @@
|
|||||||
|
package network.Messages.Enums;
|
||||||
|
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This enum encapsulates the different ways in which a client may wish to connect to a server.
|
||||||
|
*/
|
||||||
|
public enum RequestToJoinEnum {
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Client wants to spectate.
|
||||||
|
*/
|
||||||
|
SPECTATOR(0),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Client wants to participate.
|
||||||
|
*/
|
||||||
|
PARTICIPANT(1),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Client wants to play the tutorial.
|
||||||
|
*/
|
||||||
|
CONTROL_TUTORIAL(2),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Client wants to particpate as a ghost.
|
||||||
|
*/
|
||||||
|
GHOST(3),
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Used to indicate that a given byte value is invalid.
|
||||||
|
*/
|
||||||
|
NOT_A_REQUEST_TYPE(-1);
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Primitive value of the enum.
|
||||||
|
*/
|
||||||
|
private int value;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ctor. Creates a RequestToJoinEnum from a given int value.
|
||||||
|
* @param value Integer to construct from.
|
||||||
|
*/
|
||||||
|
private RequestToJoinEnum(int value) {
|
||||||
|
this.value = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the primitive value of the enum.
|
||||||
|
* @return Primitive value of the enum.
|
||||||
|
*/
|
||||||
|
public int getValue() {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stores a mapping between Integer values and RequestToJoinEnum values.
|
||||||
|
*/
|
||||||
|
private static final Map<Integer, RequestToJoinEnum> intToRequestMap = new HashMap<>();
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
Static initialization block. Initializes the intToRequestMap.
|
||||||
|
*/
|
||||||
|
static {
|
||||||
|
for (RequestToJoinEnum type : RequestToJoinEnum.values()) {
|
||||||
|
RequestToJoinEnum.intToRequestMap.put(type.value, type);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the enumeration value which corresponds to a given int value.
|
||||||
|
* @param requestToJoinEnum int value to convert to a RequestToJoinEnum value.
|
||||||
|
* @return The RequestToJoinEnum value which corresponds to the given int value.
|
||||||
|
*/
|
||||||
|
public static RequestToJoinEnum fromInt(int requestToJoinEnum) {
|
||||||
|
//Gets the corresponding MessageType from the map.
|
||||||
|
RequestToJoinEnum type = RequestToJoinEnum.intToRequestMap.get(requestToJoinEnum);
|
||||||
|
|
||||||
|
if (type == null) {
|
||||||
|
//If the int value wasn't found, return the NOT_A_REQUEST_TYPE RequestToJoinEnum.
|
||||||
|
return RequestToJoinEnum.NOT_A_REQUEST_TYPE;
|
||||||
|
} else {
|
||||||
|
//Otherwise, return the RequestToJoinEnum.
|
||||||
|
return type;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
@ -0,0 +1,54 @@
|
|||||||
|
package network.Messages;
|
||||||
|
|
||||||
|
import network.Messages.Enums.JoinAcceptanceEnum;
|
||||||
|
import network.Messages.Enums.MessageType;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This is the message a server sends to a client to tell them their boat sourceID, and if they have actually managed to join the server.
|
||||||
|
*/
|
||||||
|
public class JoinAcceptance extends AC35Data {
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The source ID of the boat assigned to the client.
|
||||||
|
* 0 indicates they haven't been assigned a boat.
|
||||||
|
*/
|
||||||
|
private int sourceID = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The type of acceptance response this is.
|
||||||
|
*/
|
||||||
|
private JoinAcceptanceEnum acceptanceType;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs a JoinAcceptance message of a given acceptance type.
|
||||||
|
* @param acceptanceType The type of join acceptance this is.
|
||||||
|
* @param sourceID The sourceID to assign to the client. 0 indicates no sourceID.
|
||||||
|
*/
|
||||||
|
public JoinAcceptance(JoinAcceptanceEnum acceptanceType, int sourceID){
|
||||||
|
super(MessageType.JOIN_ACCEPTANCE);
|
||||||
|
this.acceptanceType = acceptanceType;
|
||||||
|
this.sourceID = sourceID;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The type of acceptance response this is.
|
||||||
|
* @return The type of acceptance response.
|
||||||
|
*/
|
||||||
|
public JoinAcceptanceEnum getAcceptanceType() {
|
||||||
|
return acceptanceType;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the source ID of the boat assigned to the client.
|
||||||
|
* @return The source ID of the boat assigned to the client.
|
||||||
|
*/
|
||||||
|
public int getSourceID() {
|
||||||
|
return sourceID;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,41 @@
|
|||||||
|
package network.Messages;
|
||||||
|
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents a snapshot of the race's state.
|
||||||
|
* Contains a list of {@link AC35Data} messages.
|
||||||
|
* Send a copy of each message to a connected client.
|
||||||
|
*/
|
||||||
|
public class RaceSnapshot {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The contents of the snapshot.
|
||||||
|
*/
|
||||||
|
private List<AC35Data> snapshot;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs a snapshot using a given list of messages.
|
||||||
|
* @param snapshot Messages to use as snapshot.
|
||||||
|
*/
|
||||||
|
public RaceSnapshot(List<AC35Data> snapshot) {
|
||||||
|
this.snapshot = snapshot;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the contents of the snapshot.
|
||||||
|
* This is a shallow copy.
|
||||||
|
* @return Contents of the snapshot.
|
||||||
|
*/
|
||||||
|
public List<AC35Data> getSnapshot() {
|
||||||
|
|
||||||
|
List<AC35Data> copy = new ArrayList<>(snapshot);
|
||||||
|
|
||||||
|
return copy;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,36 @@
|
|||||||
|
package network.Messages;
|
||||||
|
|
||||||
|
|
||||||
|
import network.Messages.Enums.MessageType;
|
||||||
|
import network.Messages.Enums.RequestToJoinEnum;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This is the message a client sends to a server to request to join/view a race.
|
||||||
|
*/
|
||||||
|
public class RequestToJoin extends AC35Data {
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The type of join request this is.
|
||||||
|
*/
|
||||||
|
private RequestToJoinEnum requestType;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs a RequestToJoin message of a given request type.
|
||||||
|
* @param requestType The type of join request this is.
|
||||||
|
*/
|
||||||
|
public RequestToJoin(RequestToJoinEnum requestType){
|
||||||
|
super(MessageType.REQUEST_TO_JOIN);
|
||||||
|
this.requestType = requestType;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The type of join request this is.
|
||||||
|
* @return The type of join request.
|
||||||
|
*/
|
||||||
|
public RequestToJoinEnum getRequestType() {
|
||||||
|
return requestType;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,152 @@
|
|||||||
|
package network.StreamRelated;
|
||||||
|
|
||||||
|
|
||||||
|
import network.BinaryMessageDecoder;
|
||||||
|
import network.Exceptions.InvalidMessageException;
|
||||||
|
import network.Messages.AC35Data;
|
||||||
|
import shared.model.RunnableWithFramePeriod;
|
||||||
|
|
||||||
|
import java.io.*;
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.concurrent.BlockingQueue;
|
||||||
|
import java.util.logging.Level;
|
||||||
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
|
import static network.Utils.ByteConverter.bytesToShort;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This class is responsible for converting data from an input stream into a queue of {@link AC35Data} messages.
|
||||||
|
*/
|
||||||
|
public class MessageDeserialiser implements RunnableWithFramePeriod {
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The stream we're reading from.
|
||||||
|
*/
|
||||||
|
private DataInputStream inputStream;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The messages we've read.
|
||||||
|
*/
|
||||||
|
private BlockingQueue<AC35Data> messagesRead;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determines whether or not this runnable is currently running.
|
||||||
|
*/
|
||||||
|
private boolean isRunning;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs a new MessageSerialiser to write a queue of messages to a given stream.
|
||||||
|
* @param inputStream The stream to write to.
|
||||||
|
* @param messagesRead The messages to send.
|
||||||
|
*/
|
||||||
|
public MessageDeserialiser(InputStream inputStream, BlockingQueue<AC35Data> messagesRead) {
|
||||||
|
this.inputStream = new DataInputStream(inputStream);
|
||||||
|
this.messagesRead = messagesRead;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the queue of messages read from the socket.
|
||||||
|
* @return Queue of messages read from socket.
|
||||||
|
*/
|
||||||
|
public BlockingQueue<AC35Data> getMessagesRead() {
|
||||||
|
return messagesRead;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reads and returns the next message as an array of bytes from the input stream. Use getNextMessage() to get the actual message object instead.
|
||||||
|
* @return Encoded binary message bytes.
|
||||||
|
* @throws IOException Thrown when an error occurs while reading from the input stream.
|
||||||
|
*/
|
||||||
|
private byte[] getNextMessageBytes() throws IOException {
|
||||||
|
inputStream.mark(0);
|
||||||
|
short CRCLength = 4;
|
||||||
|
short headerLength = 15;
|
||||||
|
|
||||||
|
//Read the header of the next message.
|
||||||
|
byte[] headerBytes = new byte[headerLength];
|
||||||
|
inputStream.readFully(headerBytes);
|
||||||
|
|
||||||
|
//Read the message body length.
|
||||||
|
byte[] messageBodyLengthBytes = Arrays.copyOfRange(headerBytes, headerLength - 2, headerLength);
|
||||||
|
short messageBodyLength = bytesToShort(messageBodyLengthBytes);
|
||||||
|
|
||||||
|
//Read the message body.
|
||||||
|
byte[] messageBodyBytes = new byte[messageBodyLength];
|
||||||
|
inputStream.readFully(messageBodyBytes);
|
||||||
|
|
||||||
|
//Read the message CRC.
|
||||||
|
byte[] messageCRCBytes = new byte[CRCLength];
|
||||||
|
inputStream.readFully(messageCRCBytes);
|
||||||
|
|
||||||
|
//Put the head + body + crc into one large array.
|
||||||
|
ByteBuffer messageBytes = ByteBuffer.allocate(headerBytes.length + messageBodyBytes.length + messageCRCBytes.length);
|
||||||
|
messageBytes.put(headerBytes);
|
||||||
|
messageBytes.put(messageBodyBytes);
|
||||||
|
messageBytes.put(messageCRCBytes);
|
||||||
|
|
||||||
|
return messageBytes.array();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reads and returns the next message object from the input stream.
|
||||||
|
* @return The message object.
|
||||||
|
* @throws IOException Thrown when an error occurs while reading from the input stream.
|
||||||
|
* @throws InvalidMessageException Thrown when the message is invalid in some way.
|
||||||
|
*/
|
||||||
|
private AC35Data getNextMessage() throws IOException, InvalidMessageException
|
||||||
|
{
|
||||||
|
//Get the next message from the socket as a block of bytes.
|
||||||
|
byte[] messageBytes = this.getNextMessageBytes();
|
||||||
|
|
||||||
|
//Decode the binary message into an appropriate message object.
|
||||||
|
BinaryMessageDecoder decoder = new BinaryMessageDecoder(messageBytes);
|
||||||
|
|
||||||
|
return decoder.decode();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determines whether or not this runnable is running.
|
||||||
|
* @return True means that it is still running, false means that it has stopped.
|
||||||
|
*/
|
||||||
|
public boolean isRunning() {
|
||||||
|
return isRunning;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
|
||||||
|
isRunning = true;
|
||||||
|
|
||||||
|
while (!Thread.interrupted()) {
|
||||||
|
|
||||||
|
//Reads the next message.
|
||||||
|
try {
|
||||||
|
AC35Data message = this.getNextMessage();
|
||||||
|
messagesRead.add(message);
|
||||||
|
}
|
||||||
|
catch (InvalidMessageException e) {
|
||||||
|
Logger.getGlobal().log(Level.WARNING, "Unable to read message on thread: " + Thread.currentThread() + ".", e);
|
||||||
|
|
||||||
|
} catch (IOException e) {
|
||||||
|
Logger.getGlobal().log(Level.SEVERE, "Unable to read inputStream: " + inputStream + " on thread: " + Thread.currentThread() + ".", e);
|
||||||
|
isRunning = false;
|
||||||
|
return;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,125 @@
|
|||||||
|
package network.StreamRelated;
|
||||||
|
|
||||||
|
|
||||||
|
import network.Exceptions.InvalidMessageException;
|
||||||
|
import network.MessageEncoders.RaceVisionByteEncoder;
|
||||||
|
import network.Messages.AC35Data;
|
||||||
|
import shared.model.RunnableWithFramePeriod;
|
||||||
|
|
||||||
|
import java.io.DataOutputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.OutputStream;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.concurrent.BlockingQueue;
|
||||||
|
import java.util.logging.Level;
|
||||||
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This class is responsible for writing a queue of {@link network.Messages.AC35Data} messages to an output stream.
|
||||||
|
*/
|
||||||
|
public class MessageSerialiser implements RunnableWithFramePeriod {
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The stream we're writing to.
|
||||||
|
*/
|
||||||
|
private DataOutputStream outputStream;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The messages we're writing to the stream.
|
||||||
|
*/
|
||||||
|
private BlockingQueue<AC35Data> messagesToSend;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ack numbers used in messages.
|
||||||
|
*/
|
||||||
|
private int ackNumber = 1;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determines whether or not this runnable is currently running.
|
||||||
|
*/
|
||||||
|
private boolean isRunning;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs a new MessageSerialiser to write a queue of messages to a given stream.
|
||||||
|
* @param outputStream The stream to write to.
|
||||||
|
* @param messagesToSend The messages to send.
|
||||||
|
*/
|
||||||
|
public MessageSerialiser(OutputStream outputStream, BlockingQueue<AC35Data> messagesToSend) {
|
||||||
|
this.outputStream = new DataOutputStream(outputStream);
|
||||||
|
this.messagesToSend = messagesToSend;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the queue of messages to write to the socket.
|
||||||
|
* @return Queue of messages to write to the socket.
|
||||||
|
*/
|
||||||
|
public BlockingQueue<AC35Data> getMessagesToSend() {
|
||||||
|
return messagesToSend;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Increments the ackNumber value, and returns it.
|
||||||
|
* @return Incremented ackNumber.
|
||||||
|
*/
|
||||||
|
private int getNextAckNumber(){
|
||||||
|
this.ackNumber++;
|
||||||
|
|
||||||
|
return this.ackNumber;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determines whether or not this runnable is running.
|
||||||
|
* @return True means that it is still running, false means that it has stopped.
|
||||||
|
*/
|
||||||
|
public boolean isRunning() {
|
||||||
|
return isRunning;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
|
||||||
|
long previousFrameTime = System.currentTimeMillis();
|
||||||
|
|
||||||
|
isRunning = true;
|
||||||
|
|
||||||
|
while (!Thread.interrupted()) {
|
||||||
|
|
||||||
|
|
||||||
|
long currentFrameTime = System.currentTimeMillis();
|
||||||
|
waitForFramePeriod(previousFrameTime, currentFrameTime, 16);
|
||||||
|
previousFrameTime = currentFrameTime;
|
||||||
|
|
||||||
|
|
||||||
|
//Send the messages.
|
||||||
|
List<AC35Data> messages = new ArrayList<>();
|
||||||
|
messagesToSend.drainTo(messages);
|
||||||
|
|
||||||
|
for (AC35Data message : messages) {
|
||||||
|
try {
|
||||||
|
byte[] messageBytes = RaceVisionByteEncoder.encodeBinaryMessage(message, getNextAckNumber());
|
||||||
|
|
||||||
|
outputStream.write(messageBytes);
|
||||||
|
|
||||||
|
|
||||||
|
} catch (InvalidMessageException e) {
|
||||||
|
Logger.getGlobal().log(Level.WARNING, "Could not encode message: " + message, e);
|
||||||
|
|
||||||
|
} catch (IOException e) {
|
||||||
|
Logger.getGlobal().log(Level.SEVERE, "Could not write message to outputStream: " + outputStream + " on thread: " + Thread.currentThread(), e);
|
||||||
|
isRunning = false;
|
||||||
|
return;
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,43 +1,107 @@
|
|||||||
package network.Utils;
|
package network.Utils;
|
||||||
|
|
||||||
|
import shared.model.Constants;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Created by fwy13 on 28/04/17.
|
* Contains various unit conversion for encoding/decoding messages.
|
||||||
|
* Our program uses the "unpacked" units, and the over-the-wire format uses "packed" units (e.g., degrees stored as ints).
|
||||||
*/
|
*/
|
||||||
public class AC35UnitConverter {
|
public class AC35UnitConverter {
|
||||||
|
|
||||||
public static double convertGPS(int value){
|
|
||||||
//converts latitude or longitue to angle
|
/**
|
||||||
|
* Converts a packed GPSCoordinate (latitude or longitude) into the unpacked unit.
|
||||||
|
* @param value Packed lat/long value.
|
||||||
|
* @return Unpacked lat/long angle, in degrees.
|
||||||
|
*/
|
||||||
|
public static double unpackGPS(int value) {
|
||||||
return (double) value * 180.0 / 2147483648.0;//2^31 = 2147483648
|
return (double) value * 180.0 / 2147483648.0;//2^31 = 2147483648
|
||||||
}
|
}
|
||||||
|
|
||||||
public static int convertGPSToInt(double value){
|
/**
|
||||||
//converts latitude or longitue to angle
|
* Converts a latitude or longitude angle into a packed unit.
|
||||||
return (int) (value * 2147483648.0/180.0);//2^31 = 2147483648
|
* @param value The lat/long angle, in degrees, to convert.
|
||||||
|
* @return The packed value.
|
||||||
|
*/
|
||||||
|
public static int packGPS(double value) {
|
||||||
|
return (int) (value * 2147483648.0 / 180.0);//2^31 = 2147483648
|
||||||
}
|
}
|
||||||
|
|
||||||
public static double convertHeading(long value){
|
|
||||||
return (double) value * 360.0/65536.0;//2^15
|
/**
|
||||||
|
* Unpacks a heading from an int to an angle in degrees (this is a bearing).
|
||||||
|
* @param value The packed value to unpack.
|
||||||
|
* @return The unpacked value in degrees.
|
||||||
|
*/
|
||||||
|
public static double unpackHeading(int value) {
|
||||||
|
return (value * 360.0 / 65536.0);//2^15
|
||||||
}
|
}
|
||||||
|
|
||||||
public static double convertHeading(int value){
|
/**
|
||||||
return (double) value * 360.0/65536.0;//2^15
|
* Packs a heading (this is a bearing), in degrees, to a packed int value.
|
||||||
|
* @param value The heading in degrees.
|
||||||
|
* @return The packed value.
|
||||||
|
*/
|
||||||
|
public static int packHeading(double value) {
|
||||||
|
return (int) (value / 360.0 * 65536.0);//2^15
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public static double convertHeading(double value){
|
/**
|
||||||
return value * 360.0/65536.0;//2^15
|
* Unpacks a true wind angle from a short to an angle in degrees (this is an azimuth).
|
||||||
|
* @param value The packed value to unpack.
|
||||||
|
* @return The unpacked value in degrees.
|
||||||
|
*/
|
||||||
|
public static double unpackTrueWindAngle(short value) {
|
||||||
|
return (value * 180.0 / 32768.0);//-2^15 to 2^15
|
||||||
}
|
}
|
||||||
|
|
||||||
public static int encodeHeading(int value){
|
/**
|
||||||
return (int) (value / 360.0 * 65536.0);//2^15
|
* Packs a true wind angle (this is an azimuth) from an angle in degrees to a packed short value.
|
||||||
|
* @param value The unpacked value in degrees.
|
||||||
|
* @return The packed value.
|
||||||
|
*/
|
||||||
|
public static short packTrueWindAngle(double value) {
|
||||||
|
return (short) (value / 180.0 * 32768.0);//-2^15 to 2^15
|
||||||
}
|
}
|
||||||
|
|
||||||
public static int encodeHeading(double value){
|
|
||||||
return (int) (value / 360.0 * 65536.0);//2^15
|
/**
|
||||||
|
* Unpacks a speed, in millimeters per second, to a double, in knots.
|
||||||
|
* @param millimetersPerSec Speed in millimeters per second.
|
||||||
|
* @return Speed in knots.
|
||||||
|
*/
|
||||||
|
public static double unpackMMperSecToKnots(int millimetersPerSec) {
|
||||||
|
return (millimetersPerSec / Constants.KnotsToMMPerSecond);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static double convertTrueWindAngle(long value){
|
/**
|
||||||
return (double) value * 180.0/32768.0;//-2^15 to 2^15
|
* Packs a speed, in knots, into an int, in millimeters per second.
|
||||||
|
* @param speedKnots Speed in knots.
|
||||||
|
* @return Speed in millimeters per second.
|
||||||
|
*/
|
||||||
|
public static int packKnotsToMMperSec(double speedKnots) {
|
||||||
|
return (int) (speedKnots * Constants.KnotsToMMPerSecond);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Packs an average wind period, in milliseconds, into an int, in tenths of a second.
|
||||||
|
* @param periodMilliseconds Period in milliseconds.
|
||||||
|
* @return Period in tenths of a second.
|
||||||
|
*/
|
||||||
|
public static int packAverageWindPeriod(long periodMilliseconds) {
|
||||||
|
return (int) (periodMilliseconds / 100);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Unpacks an average wind period, in tenths of a second, into an long, in milliseconds.
|
||||||
|
* @param periodTenthsOfSecond Period in tenths of a second.
|
||||||
|
* @return Period in milliseconds
|
||||||
|
*/
|
||||||
|
public static long unpackAverageWindPeriod(int periodTenthsOfSecond) {
|
||||||
|
return (periodTenthsOfSecond * 100);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -0,0 +1,47 @@
|
|||||||
|
package shared.dataInput;
|
||||||
|
|
||||||
|
import shared.model.Boat;
|
||||||
|
import shared.model.Mark;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An empty {@link BoatDataSource}. Can be used to initialise a race with no data.
|
||||||
|
*/
|
||||||
|
public class EmptyBoatDataSource implements BoatDataSource {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A map of source ID to boat for all boats in the race.
|
||||||
|
*/
|
||||||
|
private final Map<Integer, Boat> boatMap = new HashMap<>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A map of source ID to mark for all marks in the race.
|
||||||
|
*/
|
||||||
|
private final Map<Integer, Mark> markerMap = new HashMap<>();
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
public EmptyBoatDataSource() {
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the boats that are going to participate in this race
|
||||||
|
* @return Dictionary of boats that are to participate in this race indexed by SourceID
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public Map<Integer, Boat> getBoats() {
|
||||||
|
return boatMap;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the marker Boats that are participating in this race
|
||||||
|
* @return Dictionary of the Markers Boats that are in this race indexed by their Source ID.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public Map<Integer, Mark> getMarkerBoats() {
|
||||||
|
return markerMap;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,129 @@
|
|||||||
|
package shared.dataInput;
|
||||||
|
|
||||||
|
import network.Messages.Enums.RaceTypeEnum;
|
||||||
|
import shared.model.CompoundMark;
|
||||||
|
import shared.model.GPSCoordinate;
|
||||||
|
import shared.model.Leg;
|
||||||
|
|
||||||
|
import java.time.ZonedDateTime;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An empty {@link RaceDataSource}. Can be used to initialise a race with no data.
|
||||||
|
*/
|
||||||
|
public class EmptyRaceDataSource implements RaceDataSource {
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The GPS coordinate of the top left of the race boundary.
|
||||||
|
*/
|
||||||
|
private GPSCoordinate mapTopLeft = new GPSCoordinate(0, 0);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The GPS coordinate of the bottom right of the race boundary.
|
||||||
|
*/
|
||||||
|
private GPSCoordinate mapBottomRight = new GPSCoordinate(0, 0);
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A list of GPS coordinates that make up the boundary of the race.
|
||||||
|
*/
|
||||||
|
private final List<GPSCoordinate> boundary = new ArrayList<>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A map between compoundMarkID and a CompoundMark for all CompoundMarks in a race.
|
||||||
|
*/
|
||||||
|
private final Map<Integer, CompoundMark> compoundMarkMap = new HashMap<>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A list of boat sourceIDs participating in the race.
|
||||||
|
*/
|
||||||
|
private final List<Integer> participants = new ArrayList<>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A list of legs in the race.
|
||||||
|
*/
|
||||||
|
private final List<Leg> legs = new ArrayList<>();
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The time that the race.xml file was created.
|
||||||
|
*/
|
||||||
|
private ZonedDateTime creationTimeDate = ZonedDateTime.now();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The time that the race should start at, if it hasn't been postponed.
|
||||||
|
*/
|
||||||
|
private ZonedDateTime raceStartTime = ZonedDateTime.now().plusMinutes(5);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether or not the race has been postponed.
|
||||||
|
*/
|
||||||
|
private boolean postpone = false;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The ID number of the race.
|
||||||
|
*/
|
||||||
|
private int raceID = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The type of the race.
|
||||||
|
*/
|
||||||
|
private RaceTypeEnum raceType = RaceTypeEnum.NOT_A_RACE_TYPE;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
public EmptyRaceDataSource() {
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
public List<GPSCoordinate> getBoundary() {
|
||||||
|
return boundary;
|
||||||
|
}
|
||||||
|
|
||||||
|
public GPSCoordinate getMapTopLeft() {
|
||||||
|
return mapTopLeft;
|
||||||
|
}
|
||||||
|
|
||||||
|
public GPSCoordinate getMapBottomRight() {
|
||||||
|
return mapBottomRight;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<Leg> getLegs() {
|
||||||
|
return legs;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<CompoundMark> getCompoundMarks() {
|
||||||
|
return new ArrayList<>(compoundMarkMap.values());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public ZonedDateTime getCreationDateTime() {
|
||||||
|
return creationTimeDate;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ZonedDateTime getStartDateTime() {
|
||||||
|
return raceStartTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getRaceId() {
|
||||||
|
return raceID;
|
||||||
|
}
|
||||||
|
|
||||||
|
public RaceTypeEnum getRaceType() {
|
||||||
|
return raceType;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean getPostponed() {
|
||||||
|
return postpone;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<Integer> getParticipants() {
|
||||||
|
return participants;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,122 @@
|
|||||||
|
package shared.dataInput;
|
||||||
|
|
||||||
|
import org.w3c.dom.Element;
|
||||||
|
import org.w3c.dom.NodeList;
|
||||||
|
import shared.enums.XMLFileType;
|
||||||
|
import shared.exceptions.InvalidRegattaDataException;
|
||||||
|
import shared.exceptions.XMLReaderException;
|
||||||
|
import shared.model.GPSCoordinate;
|
||||||
|
|
||||||
|
import java.io.InputStream;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An empty {@link RegattaDataSource}. Can be used to initialise a race with no data.
|
||||||
|
*/
|
||||||
|
public class EmptyRegattaDataSource implements RegattaDataSource {
|
||||||
|
/**
|
||||||
|
* The regatta ID.
|
||||||
|
*/
|
||||||
|
private int regattaID = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The regatta name.
|
||||||
|
*/
|
||||||
|
private String regattaName = "";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The race ID.
|
||||||
|
*/
|
||||||
|
private int raceID = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The course name.
|
||||||
|
*/
|
||||||
|
private String courseName = "";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The central latitude of the course.
|
||||||
|
*/
|
||||||
|
private double centralLatitude = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The central longitude of the course.
|
||||||
|
*/
|
||||||
|
private double centralLongitude = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The central altitude of the course.
|
||||||
|
*/
|
||||||
|
private double centralAltitude = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The UTC offset of the course.
|
||||||
|
*/
|
||||||
|
private float utcOffset = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The magnetic variation of the course.
|
||||||
|
*/
|
||||||
|
private float magneticVariation = 0;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
public EmptyRegattaDataSource() {
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
public int getRegattaID() {
|
||||||
|
return regattaID;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public String getRegattaName() {
|
||||||
|
return regattaName;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public int getRaceID() {
|
||||||
|
return raceID;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public String getCourseName() {
|
||||||
|
return courseName;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public double getCentralLatitude() {
|
||||||
|
return centralLatitude;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public double getCentralLongitude() {
|
||||||
|
return centralLongitude;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public double getCentralAltitude() {
|
||||||
|
return centralAltitude;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public float getUtcOffset() {
|
||||||
|
return utcOffset;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public float getMagneticVariation() {
|
||||||
|
return magneticVariation;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the GPS coorindates of the centre of the regatta.
|
||||||
|
* @return The gps coordinate for the centre of the regatta.
|
||||||
|
*/
|
||||||
|
public GPSCoordinate getGPSCoordinate() {
|
||||||
|
return new GPSCoordinate(centralLatitude, centralLongitude);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,15 @@
|
|||||||
|
package shared.exceptions;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An exception thrown when a specific boat cannot be found.
|
||||||
|
*/
|
||||||
|
public class BoatNotFoundException extends Exception {
|
||||||
|
|
||||||
|
public BoatNotFoundException(String message) {
|
||||||
|
super(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
public BoatNotFoundException(String message, Throwable cause) {
|
||||||
|
super(message, cause);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,24 @@
|
|||||||
|
package shared.exceptions;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An exception thrown when we the client-server handshake fails.
|
||||||
|
*/
|
||||||
|
public class HandshakeException extends Exception {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs the exception with a given message.
|
||||||
|
* @param message Message to store.
|
||||||
|
*/
|
||||||
|
public HandshakeException(String message) {
|
||||||
|
super(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs the exception with a given message and cause.
|
||||||
|
* @param message Message to store.
|
||||||
|
* @param cause Cause to store.
|
||||||
|
*/
|
||||||
|
public HandshakeException(String message, Throwable cause) {
|
||||||
|
super(message, cause);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,15 @@
|
|||||||
|
package shared.exceptions;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An exception thrown when a specific mark cannot be found.
|
||||||
|
*/
|
||||||
|
public class MarkNotFoundException extends Exception {
|
||||||
|
|
||||||
|
public MarkNotFoundException(String message) {
|
||||||
|
super(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
public MarkNotFoundException(String message, Throwable cause) {
|
||||||
|
super(message, cause);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,109 @@
|
|||||||
|
package shared.model;
|
||||||
|
|
||||||
|
|
||||||
|
import javafx.animation.AnimationTimer;
|
||||||
|
import javafx.beans.property.IntegerProperty;
|
||||||
|
import javafx.beans.property.SimpleIntegerProperty;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This class is used to track the framerate of something.
|
||||||
|
* Use {@link #incrementFps(long)} to update it, and {@link #fpsProperty()} to observe it.
|
||||||
|
*/
|
||||||
|
public class FrameRateTracker {
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The number of frames per second.
|
||||||
|
* We essentially track the number of frames generated per second, over a one second period. When {@link #lastFpsResetTime} reaches 1 second, {@link #currentFps} is reset.
|
||||||
|
*/
|
||||||
|
private int currentFps = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The number of frames per second we generated over the last 1 second period.
|
||||||
|
*/
|
||||||
|
private IntegerProperty lastFps = new SimpleIntegerProperty(0);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The time, in milliseconds, since we last reset our {@link #currentFps} counter.
|
||||||
|
*/
|
||||||
|
private long lastFpsResetTime;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a {@link FrameRateTracker}. Use {@link #incrementFps(long)} to update it, and {@link #fpsProperty()} to observe it.
|
||||||
|
*/
|
||||||
|
public FrameRateTracker() {
|
||||||
|
timer.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the number of frames generated per second.
|
||||||
|
* @return Frames per second.
|
||||||
|
*/
|
||||||
|
public int getFps() {
|
||||||
|
return lastFps.getValue();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the fps property.
|
||||||
|
* @return The fps property.
|
||||||
|
*/
|
||||||
|
public IntegerProperty fpsProperty() {
|
||||||
|
return lastFps;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Increments the FPS counter, and adds timePeriod milliseconds to our FPS reset timer.
|
||||||
|
* @param timePeriod Time, in milliseconds, to add to {@link #lastFpsResetTime}.
|
||||||
|
*/
|
||||||
|
private void incrementFps(long timePeriod) {
|
||||||
|
//Increment.
|
||||||
|
this.currentFps++;
|
||||||
|
|
||||||
|
//Add period to timer.
|
||||||
|
this.lastFpsResetTime += timePeriod;
|
||||||
|
|
||||||
|
//If we have reached 1 second period, snapshot the framerate and reset.
|
||||||
|
if (this.lastFpsResetTime > 1000) {
|
||||||
|
this.lastFps.set(this.currentFps);
|
||||||
|
|
||||||
|
this.currentFps = 0;
|
||||||
|
this.lastFpsResetTime = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Timer used to update the framerate.
|
||||||
|
* This is used because we care about frames in the javaFX thread.
|
||||||
|
*/
|
||||||
|
private AnimationTimer timer = new AnimationTimer() {
|
||||||
|
|
||||||
|
long previousFrameTime = System.currentTimeMillis();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void handle(long now) {
|
||||||
|
|
||||||
|
long currentFrameTime = System.currentTimeMillis();
|
||||||
|
long framePeriod = currentFrameTime - previousFrameTime;
|
||||||
|
|
||||||
|
//Increment fps.
|
||||||
|
incrementFps(framePeriod);
|
||||||
|
|
||||||
|
previousFrameTime = currentFrameTime;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stops the {@link FrameRateTracker}'s timer.
|
||||||
|
*/
|
||||||
|
public void stop() {
|
||||||
|
timer.stop();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in new issue