LatestMessages can hold MarkRounding messages. It also holds XMLMessage for each message type.

MockOutput now uses LatestMessages for xml messages.
Moved xml message encoding into RaceVisionByteEncoder. Removed XMLMessageEncoder.
Moved XML message sub types into an enumeration (XMLMessageType).
XMLMessage can now be queried for its attributes (like timestamp, length, etc...).

VisualiserInput now uses LatestMessages.
VisualiserInput now (as it did previously) uses switch statements for checking packet type, instead of if statements.
VisualiserRace now uses LatestMessages instead of VisualiserInput.
main
fjc40 9 years ago
parent 8e18ad62ca
commit f057ad58b7

@ -11,7 +11,7 @@
<module>mock</module>
<module>visualiser</module>
<module>network</module>
<module>sharedModel</module>
<module>racevisionGame</module>
</modules>
<url>https://eng-git.canterbury.ac.nz/SENG302-2016/team-7</url>

@ -45,6 +45,12 @@
<version>9.0</version>
</dependency>
<dependency>
<groupId>com.github.bfsmith</groupId>
<artifactId>geotimezone</artifactId>
<version>1.0.3</version>
</dependency>
</dependencies>

@ -3,7 +3,9 @@ package mock.app;
import mock.model.MockRace;
import mock.model.Polars;
import network.Messages.Enums.MessageType;
import network.Messages.Enums.XMLMessageType;
import network.Messages.LatestMessages;
import network.Messages.XMLMessage;
import org.xml.sax.SAXException;
import shared.dataInput.*;
import shared.exceptions.InvalidBoatDataException;
@ -79,13 +81,10 @@ public class Event {
private void sendXMLs() {
mockOutput.setRegattaXml(regattaXML);
mockOutput.parseXMLString(regattaXML, MessageType.XMLMESSAGE.getValue());
mockOutput.setRaceXml(raceXML);
mockOutput.parseXMLString(raceXML, MessageType.XMLMESSAGE.getValue());
mockOutput.setBoatsXml(boatXML);
mockOutput.parseXMLString(boatXML, MessageType.XMLMESSAGE.getValue());
}
/**

@ -4,38 +4,46 @@ package mock.app;
import network.BinaryMessageEncoder;
import network.MessageEncoders.RaceVisionByteEncoder;
import network.MessageEncoders.XMLMessageEncoder;
import network.Messages.BoatLocation;
import network.Messages.*;
import network.Messages.Enums.MessageType;
import network.Messages.LatestMessages;
import network.Messages.RaceStatus;
import network.Messages.XMLMessage;
import network.Messages.Enums.XMLMessageType;
import java.io.DataOutputStream;
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.SocketException;
import java.util.concurrent.ArrayBlockingQueue;
/**
* TCP server to send race information to connected clients.
*/
public class MockOutput implements Runnable
{
///Timestamp of the last sent heartbeat message.
/**
* Timestamp of the last sent heartbeat message.
*/
private long lastHeartbeatTime;
///Period for the heartbeat - that is, how often we send it.
/**
* Period for the heartbeat - that is, how often we send it.
*/
private double heartbeatPeriod = 5.0;
///Port to expose server on.
/**
* Port to expose server on.
*/
private int serverPort = 4942;
///Socket used to listen for clients on.
/**
* Socket used to listen for clients on.
*/
private ServerSocket serverSocket;
///Socket used to communicate with a client.
/**
* Socket used to communicate with a client.
*/
private Socket mockSocket;
///Output stream which wraps around mockSocket outstream.
/**
* Output stream which wraps around mockSocket outstream.
*/
private DataOutputStream outToVisualiser;
@ -47,17 +55,32 @@ public class MockOutput implements Runnable
///Sequence numbers used in messages.
private short messageNumber = 1;
private short xmlSequenceNumber = 1;
/**
* Ack numbers used in messages.
*/
private int ackNumber = 1;
/**
* Sequence number for race xml messages.
*/
private short raceXMLSequenceNumber = 1;
/**
* Sequence number for boat xml messages.
*/
private short boatXMLSequenceNumber = 1;
/**
* Sequence number for regatta xml messages.
*/
private short regattaXMLSequenceNumber = 1;
/**
* Sequence number for heartbeat messages.
*/
private int heartbeatSequenceNum = 1;
private int boatLocationSequenceNumber = 1;
private int raceStatusSequenceNumber = 1;
///Strings containing XML data as strings.
private String raceXml;
private String regattaXml;
private String boatsXml;
private boolean stop = false; //whether or not hte thread keeps running
@ -74,6 +97,19 @@ public class MockOutput implements Runnable
this.latestMessages = latestMessages;
}
/**
* Increments the ackNumber value, and returns it.
* @return Incremented ackNumber.
*/
private int getNextAckNumber(){
this.ackNumber++;
return this.ackNumber;
}
/**
* Calculates the time since last heartbeat message, in seconds.
* @return Time since last heartbeat message, in seconds.
@ -83,56 +119,105 @@ public class MockOutput implements Runnable
return (now - lastHeartbeatTime) / 1000.0;
}
//returns the heartbeat message
/**
* Increment the heartbeat value
* @return message for heartbeat data
* Generates the next heartbeat message and returns it. Increments the heartbeat sequence number.
* @return The next heartbeat message.
*/
private byte[] heartbeat(){
byte[] heartbeatMessage = RaceVisionByteEncoder.heartBeat(heartbeatSequenceNum);
private Heartbeat createHeartbeatMessage() {
//Create the heartbeat message.
Heartbeat heartbeat = new Heartbeat(this.heartbeatSequenceNum);
heartbeatSequenceNum++;
BinaryMessageEncoder binaryMessageEncoder = new BinaryMessageEncoder(MessageType.HEARTBEAT, System.currentTimeMillis(), messageNumber, (short)heartbeatMessage.length, heartbeatMessage);
messageNumber++;
return heartbeat;
}
/**
* Serializes a heartbeat message into a packet to be sent, and returns the byte array.
* @param heartbeat The heartbeat message to serialize.
* @return Byte array containing the next heartbeat message.
*/
private byte[] parseHeartbeat(Heartbeat heartbeat) {
//Serializes the heartbeat message.
byte[] heartbeatMessage = RaceVisionByteEncoder.heartBeat(heartbeat);
//Places the serialized message in a packet.
BinaryMessageEncoder binaryMessageEncoder = new BinaryMessageEncoder(
MessageType.HEARTBEAT,
System.currentTimeMillis(),
getNextAckNumber(),
(short) heartbeatMessage.length,
heartbeatMessage );
return binaryMessageEncoder.getFullMessage();
}
/**
* Used to give the mockOutput an xml string to be made into a message and sent
* @param xmlString the xml string to send
* @param messageType the kind of xml string, values given in AC35 spec (5 regatta, 6 race, 7 boat)
* Creates an XMLMessage of a specified subtype using the xml contents string.
* @param xmlString The contents of the xml file.
* @param messageType The subtype of xml message (race, regatta, boat).
* @return The created XMLMessage object.
*/
public synchronized byte[] parseXMLString(String xmlString, int messageType) {
private XMLMessage createXMLMessage(String xmlString, XMLMessageType messageType) {
//Get the correct sequence number to use, and increment it.
short sequenceNumber = 0;
if (messageType == XMLMessageType.RACE) {
sequenceNumber = this.raceXMLSequenceNumber;
this.raceXMLSequenceNumber++;
} else if (messageType == XMLMessageType.BOAT) {
sequenceNumber = this.boatXMLSequenceNumber;
this.boatXMLSequenceNumber++;
} else if (messageType == XMLMessageType.REGATTA) {
sequenceNumber = this.regattaXMLSequenceNumber;
this.regattaXMLSequenceNumber++;
XMLMessageEncoder encoder = new XMLMessageEncoder(
messageNumber,
}
//Create the message.
XMLMessage message = new XMLMessage(
XMLMessage.currentVersionNumber,
getNextAckNumber(),
System.currentTimeMillis(),
messageType,
xmlSequenceNumber,
(short) xmlString.length(),
xmlString);
sequenceNumber,
xmlString );
//iterates the sequence numbers
xmlSequenceNumber++;
return message;
}
/**
* Encodes/serialises a XMLMessage message, and returns it.
* @param xmlMessage The XMLMessage message to serialise.
* @return The XMLMessage message in a serialised form.
*/
private synchronized byte[] parseXMLMessage(XMLMessage xmlMessage) {
byte[] encodedXML = encoder.encode();
//Serialize the xml message.
byte[] encodedXML = RaceVisionByteEncoder.xmlMessage(xmlMessage);
//Place the message in a packet.
BinaryMessageEncoder binaryMessageEncoder = new BinaryMessageEncoder(
MessageType.XMLMESSAGE,
System.currentTimeMillis(),
messageNumber,
(short)encodedXML.length,
encodedXML);
xmlMessage.getAckNumber(), //We use the ack number from the xml message.
(short) encodedXML.length,
encodedXML );
//iterates the message number
messageNumber++;
return binaryMessageEncoder.getFullMessage();
}
/**
* Encodes/serialises a BoatLocation message, and returns it.
* @param boatLocation The BoatLocation message to serialise.
@ -148,12 +233,10 @@ public class MockOutput implements Runnable
BinaryMessageEncoder binaryMessageEncoder = new BinaryMessageEncoder(
MessageType.BOATLOCATION,
System.currentTimeMillis(),
messageNumber,
getNextAckNumber(),
(short) encodedBoatLoc.length,
encodedBoatLoc );
//iterates the message number
messageNumber++;
return binaryMessageEncoder.getFullMessage();
@ -166,9 +249,6 @@ public class MockOutput implements Runnable
*/
private synchronized byte[] parseRaceStatus(RaceStatus raceStatus){
//iterates the sequence number
raceStatusSequenceNumber++;
//Encodes the messages.
byte[] encodedRaceStatus = RaceVisionByteEncoder.raceStatus(raceStatus);
@ -176,12 +256,10 @@ public class MockOutput implements Runnable
BinaryMessageEncoder binaryMessageEncoder = new BinaryMessageEncoder(
MessageType.RACESTATUS,
System.currentTimeMillis(),
messageNumber,
getNextAckNumber(),
(short) encodedRaceStatus.length,
encodedRaceStatus );
//iterates the message number
messageNumber++;
return binaryMessageEncoder.getFullMessage();
@ -196,12 +274,14 @@ public class MockOutput implements Runnable
try {
while (!stop){
//Wait for a client to connect.
System.out.println("Waiting for a connection...");//TEMP DEBUG REMOVE
mockSocket = serverSocket.accept();
outToVisualiser = new DataOutputStream(mockSocket.getOutputStream());
if (boatsXml == null || regattaXml == null || raceXml == null) {
//Wait until all of the xml files have been set.
if (!this.latestMessages.hasAllXMLMessages()) {
try {
Thread.sleep(500);
} catch (InterruptedException e) {
@ -210,11 +290,6 @@ public class MockOutput implements Runnable
continue;
}
//Encode xml files. We send them inside the loop, depending on the sentXMLs boolean.
byte[] raceXMLBlob = parseXMLString(raceXml, XMLMessage.XMLTypeRace);
byte[] regattaXMLBlob = parseXMLString(regattaXml, XMLMessage.XMLTypeRegatta);
byte[] boatsXMLBlob = parseXMLString(boatsXml, XMLMessage.XMLTypeBoat);
long previousFrameTime = System.currentTimeMillis();
boolean sentXMLs = false;
@ -234,19 +309,25 @@ public class MockOutput implements Runnable
//Sends a heartbeat every so often.
if (timeSinceHeartbeat() >= heartbeatPeriod) {
outToVisualiser.write(heartbeat());
outToVisualiser.write(parseHeartbeat(createHeartbeatMessage()));
lastHeartbeatTime = System.currentTimeMillis();
}
//Send XML messages.
if (!sentXMLs) {
//Serialise them.
byte[] raceXMLBlob = parseXMLMessage(latestMessages.getRaceXMLMessage());
byte[] regattaXMLBlob = parseXMLMessage(latestMessages.getRegattaXMLMessage());
byte[] boatsXMLBlob = parseXMLMessage(latestMessages.getBoatXMLMessage());
//Send them.
outToVisualiser.write(raceXMLBlob);
outToVisualiser.write(regattaXMLBlob);
outToVisualiser.write(boatsXMLBlob);
sentXMLs = true;
}
//Sens the RaceStatus message.
//Sends the RaceStatus message.
if (this.latestMessages.getRaceStatus() != null) {
byte[] raceStatusBlob = this.parseRaceStatus(this.latestMessages.getRaceStatus());
@ -305,29 +386,42 @@ public class MockOutput implements Runnable
}
/**
* Sets the Race XML to send
* @param raceXml XML to send to the CLient
* Sets the Race XML to send.
* @param raceXml XML to send to the Client.
*/
public void setRaceXml(String raceXml) {
this.raceXml = raceXml;
//Create the message.
XMLMessage message = this.createXMLMessage(raceXml, XMLMessageType.RACE);
//Place it in LatestMessages.
this.latestMessages.setRaceXMLMessage(message);
}
/**
* Sets the Regatta XMl to send
* @param regattaXml XML to send to CLient
* Sets the Regatta XMl to send.
* @param regattaXml XML to send to Client.
*/
public void setRegattaXml(String regattaXml) {
this.regattaXml = regattaXml;
//Create the message.
XMLMessage message = this.createXMLMessage(regattaXml, XMLMessageType.REGATTA);
//Place it in LatestMessages.
this.latestMessages.setRegattaXMLMessage(message);
}
/**
* Sets the Boats XML to send
* @param boatsXml XMl to send to the CLient
* Sets the Boats XML to send.
* @param boatsXml XMl to send to the Client.
*/
public void setBoatsXml(String boatsXml) {
this.boatsXml = boatsXml;
//Create the message.
XMLMessage message = this.createXMLMessage(boatsXml, XMLMessageType.BOAT);
//Place it in LatestMessages.
this.latestMessages.setBoatXMLMessage(message);
}
public static void main(String argv[]) throws Exception
{
MockOutput client = new MockOutput(new LatestMessages());

@ -151,7 +151,7 @@ public class BinaryMessageDecoder {
//System.out.println("XML Message!");
XMLMessageDecoder xmdecoder = new XMLMessageDecoder(messageBody);
xmdecoder.decode();
return new XMLMessage(xmdecoder.getAckNumber(), xmdecoder.getTimeStamp(), xmdecoder.getXmlMsgSubType(), xmdecoder.getSequenceNumber(), xmdecoder.getXmlMsgLength(), xmdecoder.getXmlMessageInputStream());
return new XMLMessage(XMLMessage.currentVersionNumber, xmdecoder.getAckNumber(), xmdecoder.getTimeStamp(), xmdecoder.getXmlMsgSubType(), xmdecoder.getSequenceNumber(), xmdecoder.getXmlMessageContents());
case RACESTARTSTATUS:
//System.out.println("Race Start Status Message");

@ -1,5 +1,7 @@
package network.MessageDecoders;
import network.Messages.Enums.XMLMessageType;
import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
@ -42,7 +44,7 @@ public class XMLMessageDecoder {
this.sequenceNumber = bytesToShort(sequenceNumberBytes);
this.xmlMsgLength = bytesToShort(xmlMsgLengthBytes);
this.xmlMessage = new String(xmlMessagebytes);
this.xmlMessage = new String(xmlMessagebytes).trim();
}
public byte getMessageVersionNumber() {
@ -57,8 +59,8 @@ public class XMLMessageDecoder {
return timeStamp;
}
public byte getXmlMsgSubType() {
return xmlMsgSubType;
public XMLMessageType getXmlMsgSubType() {
return XMLMessageType.fromByte(xmlMsgSubType);
}
public short getSequenceNumber() {
@ -69,14 +71,14 @@ public class XMLMessageDecoder {
return xmlMsgLength;
}
/**
* this will be used latter for the vis
* @return xml string as inputsource
* Returns the contents of the XML message (e.g., the contents of a race.xml file).
* @return The contents of the XML message.
*/
public InputStream getXmlMessageInputStream() {
InputStream is = new ByteArrayInputStream(xmlMessage.trim().getBytes(StandardCharsets.UTF_8));
// InputSource is = new InputSource(new StringReader(xmlMessage.trim()));
return is;
public String getXmlMessageContents() {
return xmlMessage;
}
}

@ -6,6 +6,7 @@ import network.Messages.*;
import static network.Utils.ByteConverter.*;
import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
@ -18,13 +19,16 @@ public class RaceVisionByteEncoder {
/**
* Serializes a heartbeat message.
* @param seq Heartbeat value.
* @param heartbeat Heartbeat message.
* @return Serialized message.
*/
public static byte[] heartBeat(long seq){
public static byte[] heartBeat(Heartbeat heartbeat) {
ByteBuffer heartBeat = ByteBuffer.allocate(4);
heartBeat.put(longToBytes(seq, 4));
byte [] result = heartBeat.array();
heartBeat.put(longToBytes(heartbeat.getSequenceNumber(), 4));
byte[] result = heartBeat.array();
return result;
}
@ -176,6 +180,44 @@ public class RaceVisionByteEncoder {
return result.array();
}
/**
* Serializes an xml message into a byte array.
* @param xmlMessage The message to serialize.
* @return byte array contaning serialized message.
*/
public static byte[] xmlMessage(XMLMessage xmlMessage) {
byte[] messageBytes = xmlMessage.getXmlMessage().getBytes(StandardCharsets.UTF_8);
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();
}
public static byte[] boatLocation(BoatLocation boatLocation){
int messageVersionNumber = 0b1;
byte[] time = longToBytes(boatLocation.getTime(), 6);

@ -1,59 +0,0 @@
package network.MessageEncoders;
import java.nio.ByteBuffer;
import static network.Utils.ByteConverter.*;
/**
* Encodes a XML file into a message of AC35 format
*/
public class XMLMessageEncoder {
private byte[] messageVersionNumber;
private short ackNumber;
private long timeStamp;
private byte[] xmlMsgSubType;
private short sequenceNumber;
private short xmlMsgLength;
private String xmlMessage;
public XMLMessageEncoder(short ackNumber, long timeStamp, int xmlMsgSubType, short sequenceNumber, short xmlMsgLength, String xmlMessage) {
this.messageVersionNumber = intToBytes(1, 1);
this.ackNumber = ackNumber;
this.timeStamp = timeStamp;
this.xmlMsgSubType = intToBytes(xmlMsgSubType, 1);
this.sequenceNumber = sequenceNumber;
this.xmlMsgLength = xmlMsgLength;
this.xmlMessage = xmlMessage;
}
public byte[] encode() {
byte[] messageBytes = xmlMessage.getBytes();
if (messageBytes.length > this.xmlMsgLength) {
//System.err.println("Xml message is to big");
return null;
}
ByteBuffer tempOutputByteBuffer = ByteBuffer.allocate(14 + messageBytes.length);
//ackNumber converted to bytes
byte[] ackNumberBytes = shortToBytes(ackNumber, 2);
//sequenceNumber converted to bytes
byte[] sequenceNumberBytes = shortToBytes(sequenceNumber, 2);
//xmlMsgLength converted to bytes
byte[] xmlMsgLengthBytes = shortToBytes(xmlMsgLength, 2);
tempOutputByteBuffer.put(messageVersionNumber);
tempOutputByteBuffer.put(ackNumberBytes);
tempOutputByteBuffer.put(longToBytes(timeStamp, 6));
tempOutputByteBuffer.put(xmlMsgSubType);
tempOutputByteBuffer.put(sequenceNumberBytes);
tempOutputByteBuffer.put(xmlMsgLengthBytes);
tempOutputByteBuffer.put(messageBytes);
return tempOutputByteBuffer.array();
}
}

@ -8,7 +8,9 @@ import network.Messages.Enums.MessageType;
*/
public abstract class AC35Data {
///Message type from the header.
/**
* Message type from the header.
*/
private MessageType type;

@ -0,0 +1,87 @@
package network.Messages.Enums;
import java.util.HashMap;
import java.util.Map;
/**
* Enumeration that encapsulates the various types of XML messages that can be sent.
*/
public enum XMLMessageType {
/**
* A regatta.xml message.
*/
REGATTA(5),
/**
* A race.xml message.
*/
RACE(6),
/**
* A boats.xml message.
*/
BOAT(7),
/**
* Used for unrecognised byte values.
*/
NOT_A_MESSAGE_TYPE(0);
///Primitive value of the enum.
private byte value;
/**
* Ctor. Creates a XMLMessageType enum from a given primitive integer value, cast to a byte.
* @param value Integer, which is cast to byte, to construct from.
*/
private XMLMessageType(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 XMLMessageType values.
private static final Map<Byte, XMLMessageType> byteToTypeMap = new HashMap<>();
/*
Static initialization block. Initializes the byteToTypeMap.
*/
static {
for (XMLMessageType type : XMLMessageType.values()) {
byteToTypeMap.put(type.value, type);
}
}
/**
* Returns the enumeration value which corresponds to a given byte value.
* @param messageTypeByte Byte value to convert to a XMLMessageType value.
* @return The XMLMessageType value which corresponds to the given byte value.
*/
public static XMLMessageType fromByte(byte messageTypeByte) {
//Gets the corresponding XMLMessageType from the map.
XMLMessageType type = byteToTypeMap.get(messageTypeByte);
if (type == null) {
//If the byte value wasn't found, return the NOTAMESSAGE XMLMessageType.
return XMLMessageType.NOT_A_MESSAGE_TYPE;
}
else {
//Otherwise, return the XMLMessageType.
return type;
}
}
}

@ -8,7 +8,9 @@ import network.Messages.Enums.MessageType;
*/
public class Heartbeat extends AC35Data {
///Sequence number of the heartbeat.
/**
* Sequence number of the heartbeat.
*/
private long sequenceNumber;
/**

@ -1,5 +1,8 @@
package network.Messages;
import network.Messages.Enums.XMLMessageType;
import shared.dataInput.RaceDataSource;
import java.util.HashMap;
import java.util.Map;
@ -23,6 +26,11 @@ public class LatestMessages {
*/
private final Map<Integer, BoatLocation> boatLocationMap = new HashMap<>();
/**
* A map of the last MarkRounding message received, for each boat.
*/
private final Map<Integer, MarkRounding> markRoundingMap = new HashMap<>();
/**
* The last AverageWind message received.
*/
@ -34,6 +42,22 @@ public class LatestMessages {
private CourseWinds courseWinds;
/**
* The latest race data XML message.
*/
private XMLMessage raceXMLMessage;
/**
* The latest boat data XML message.
*/
private XMLMessage boatXMLMessage;
/**
* The latest regatta data XML message.
*/
private XMLMessage regattaXMLMessage;
/**
@ -100,6 +124,24 @@ public class LatestMessages {
boatLocationMap.put(boatLocation.getSourceID(), boatLocation);
}
/**
* Returns the latest MarkRounding message received for a given boat.
* @param sourceID Source ID of the boat.
* @return The latest MarkRounding message for the specified boat.
*/
public MarkRounding getMarkRounding(int sourceID) {
return markRoundingMap.get(sourceID);
}
/**
* Inserts a MarkRounding message for a given boat.
* @param markRounding The MarkRounding message to set.
*/
public void setMarkRounding(MarkRounding markRounding) {
//TODO should compare the sequence number of the new boatLocation with the existing boatLocation for this boat (if it exists), and use the newer one.
markRoundingMap.put(markRounding.getSourceID(), markRounding);
}
/**
@ -136,7 +178,115 @@ public class LatestMessages {
}
/**
* Returns the map of boat sourceIDs to BoatLocation messages.
* @return Map between boat sourceID and BoatLocation.
*/
public Map<Integer, BoatLocation> getBoatLocationMap() {
return boatLocationMap;
}
/**
* Returns the map of boat sourceIDs to BoatStatus messages.
* @return Map between boat sourceID and BoatStatus.
*/
public Map<Integer, BoatStatus> getBoatStatusMap() {
return boatStatusMap;
}
/**
* Returns the map of boat sourceIDs to MarkRounding messages.
* @return Map between boat sourceID and MarkRounding.
*/
public Map<Integer, MarkRounding> getMarkRoundingMap() {
return markRoundingMap;
}
/**
* Returns the latest race xml message.
* @return The latest race xml message.
*/
public XMLMessage getRaceXMLMessage() {
return raceXMLMessage;
}
/**
* Sets the latest race xml message to a specified race XML message.
* @param raceXMLMessage The new race XML message to use.
*/
public void setRaceXMLMessage(XMLMessage raceXMLMessage) {
this.raceXMLMessage = raceXMLMessage;
}
/**
* Returns the latest boat xml message.
* @return The latest boat xml message.
*/
public XMLMessage getBoatXMLMessage() {
return boatXMLMessage;
}
/**
* Sets the latest boat xml message to a specified boat XML message.
* @param boatXMLMessage The new boat XML message to use.
*/
public void setBoatXMLMessage(XMLMessage boatXMLMessage) {
this.boatXMLMessage = boatXMLMessage;
}
/**
* Returns the latest regatta xml message.
* @return The latest regatta xml message.
*/
public XMLMessage getRegattaXMLMessage() {
return regattaXMLMessage;
}
/**
* Sets the latest regatta xml message to a specified regatta XML message.
* @param regattaXMLMessage The new regatta XML message to use.
*/
public void setRegattaXMLMessage(XMLMessage regattaXMLMessage) {
this.regattaXMLMessage = regattaXMLMessage;
}
/**
* Checks the type of xml message, and places it in this LatestMessages object.
* @param xmlMessage The new xml message to use.
*/
public void setXMLMessage(XMLMessage xmlMessage) {
if (xmlMessage.getXmlMsgSubType() == XMLMessageType.RACE) {
this.setRaceXMLMessage(xmlMessage);
} else if (xmlMessage.getXmlMsgSubType() == XMLMessageType.REGATTA) {
this.setRegattaXMLMessage(xmlMessage);
} else if (xmlMessage.getXmlMsgSubType() == XMLMessageType.BOAT) {
this.setBoatXMLMessage(xmlMessage);
}
}
/**
* Returns whether or not there is an xml message for each message type.
* @return True if race, boat, and regatta have an xml message, false otherwise.
*/
public boolean hasAllXMLMessages() {
if ((this.regattaXMLMessage == null) || (this.boatXMLMessage == null) || (this.raceXMLMessage == null)) {
return false;
} else {
return true;
}
}
}

@ -2,49 +2,86 @@ package network.Messages;
import network.Messages.Enums.MessageType;
import network.Messages.Enums.XMLMessageType;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
/**
* Created by fwy13 on 25/04/17.
*/
public class XMLMessage extends AC35Data {
/**
* The current version number for xml messages is 1.
*/
public static byte currentVersionNumber = 1;
/**
* The version number of the message.
*/
private byte versionNumber;
/**
* The ack number of the message.
*/
private int ackNumber;
/**
* The timestamp of the message.
* Milliseconds since unix epoch.
*/
private long timeStamp;
private int xmlMsgSubType;
/**
* The subtype of the xml message (e.g., race xml message).
*/
private XMLMessageType xmlMsgSubType;
/**
* The sequence number of this specific xml subtype.
* Increments whenever the xml contents for a specific xml subtype changes.
*/
private int sequenceNumber;
/**
* The length of the xml message.
* Number of bytes.
*/
private int xmlMsgLength;
private InputStream xmlMessage;
public static int XMLTypeRegatta = 5;
public static int XMLTypeRace = 6;
public static int XMLTypeBoat = 7;
/**
* The contents of the xml message.
*/
private String xmlMessage;
/**
* Constructor for an XML Message
* @param versionNumber The version number of the xml message.
* @param ackNumber Number for acknowledgement inherited for the AC35Data Packet
* @param timeStamp Time received
* @param xmlMsgSubType Type of XML message
* @param sequenceNumber Order that it has arrived in
* @param xmlMsgLength Length of the xml message
* @param xmlMessage XML message
*/
public XMLMessage(int ackNumber, long timeStamp, int xmlMsgSubType, int sequenceNumber, int xmlMsgLength, InputStream xmlMessage){
public XMLMessage(byte versionNumber, int ackNumber, long timeStamp, XMLMessageType xmlMsgSubType, int sequenceNumber, String xmlMessage) {
super(MessageType.XMLMESSAGE);
this.versionNumber = versionNumber;
this.ackNumber = ackNumber;
this.timeStamp = timeStamp;
this.xmlMsgSubType = xmlMsgSubType;
this.sequenceNumber = sequenceNumber;
this.xmlMsgLength = xmlMsgLength;
this.xmlMsgLength = xmlMessage.getBytes(StandardCharsets.UTF_8).length;
this.xmlMessage = xmlMessage;
}
/**
* Get the XML Message
* @return the XML message as an input stream
* Get the XML Message.
* @return the XML message as string.
*/
public InputStream getXmlMessage() {
public String getXmlMessage() {
return xmlMessage;
}
@ -52,7 +89,48 @@ public class XMLMessage extends AC35Data {
* Get the type of message
* @return Gets the type of message the XML message is
*/
public int getXmlMsgSubType() {
public XMLMessageType getXmlMsgSubType() {
return xmlMsgSubType;
}
/**
* Returns the version number of this xml message.
* @return The version number of this xml message.
*/
public byte getVersionNumber() {
return versionNumber;
}
/**
* Returns the ack number of this xml message.
* @return The ack number of this xml message.
*/
public int getAckNumber() {
return ackNumber;
}
/**
* Returns the timestamp of this xml message.
* @return The timestamp of this xml message.
*/
public long getTimeStamp() {
return timeStamp;
}
/**
* Returns the sequence number of this xml message. This is specific to each message subtype.
* @return The sequence number of this xml message.
*/
public int getSequenceNumber() {
return sequenceNumber;
}
/**
* Returns the length, in number of bytes, of the xml message.
* @return The length, in bytes, of the xml message.
*/
public int getXmlMsgLength() {
return xmlMsgLength;
}
}

@ -48,10 +48,15 @@ public abstract class Race implements Runnable {
protected LatestMessages latestMessages;
/**
* The sequence number of the latest boatLocation message sent or received.
* The sequence number of the latest BoatLocation message sent or received.
*/
protected int boatLocationSequenceNumber = 1;
/**
* The sequence number of the latest RaceStatus message sent or received.
*/
protected int raceStatusSequenceNumber = 1;
/**

@ -8,7 +8,7 @@ import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.control.TextField;
import javafx.scene.layout.AnchorPane;
import seng302.RaceConnection;
import visualiser.model.RaceConnection;
import java.io.IOException;
import java.net.Socket;
@ -22,7 +22,7 @@ public class ConnectionController extends Controller {
@FXML
private AnchorPane connectionWrapper;
@FXML
private TableView connectionTable;
private TableView<RaceConnection> connectionTable;
@FXML
private TableColumn<RaceConnection, String> hostnameColumn;
@FXML
@ -73,7 +73,7 @@ public class ConnectionController extends Controller {
*/
public void connectSocket() {
try{
RaceConnection connection = (RaceConnection)connectionTable.getSelectionModel().getSelectedItem();
RaceConnection connection = connectionTable.getSelectionModel().getSelectedItem();
Socket socket = new Socket(connection.getHostname(), connection.getPort());
connectionWrapper.setVisible(false);
parent.enterLobby(socket);

@ -6,6 +6,7 @@ import javafx.scene.layout.AnchorPane;
import seng302.Model.Boat;
import seng302.Model.RaceClock;
import seng302.VisualiserInput;
import visualiser.model.VisualiserBoat;
import java.net.Socket;
import java.net.URL;
@ -28,7 +29,9 @@ public class MainController extends Controller {
startController.enterLobby(socket);
}
public void enterFinish(ObservableList<Boat> boats) { finishController.enterFinish(boats); }
public void enterFinish(ObservableList<VisualiserBoat> boats) {
finishController.enterFinish(boats);
}
/**
* Main Controller for the applications will house the menu and the displayed pane.

@ -14,6 +14,7 @@ import javafx.scene.layout.GridPane;
import visualiser.app.VisualiserInput;
import visualiser.model.RaceClock;
import visualiser.model.VisualiserBoat;
import visualiser.model.VisualiserRace;
import java.io.IOException;
import java.net.Socket;
@ -48,6 +49,11 @@ public class StartController extends Controller implements Observer {
private VisualiserInput visualiserInput;
/**
* The race object which describes the currently occurring race.
*/
private VisualiserRace visualiserRace;
///Tracks whether the race has been started (that is, has startRaceNoScaling() be called).
private boolean hasRaceStarted = false;
@ -65,7 +71,7 @@ public class StartController extends Controller implements Observer {
@Override
public void initialize(URL location, ResourceBundle resources){
raceData = new StreamedCourse();
this.visualiserRace = new VisualiserRace();
raceData.addObserver(this);
}

@ -1,7 +1,16 @@
package visualiser.app;
import javafx.application.Platform;
import network.BinaryMessageDecoder;
import network.Exceptions.InvalidMessageException;
import network.Messages.*;
import org.xml.sax.SAXException;
import shared.dataInput.BoatXMLReader;
import shared.dataInput.RaceXMLReader;
import shared.dataInput.RegattaXMLReader;
import shared.exceptions.InvalidBoatDataException;
import shared.exceptions.InvalidRaceDataException;
import shared.exceptions.InvalidRegattaDataException;
import shared.exceptions.XMLReaderException;
import javax.xml.parsers.ParserConfigurationException;
import java.io.DataInputStream;
@ -12,78 +21,65 @@ import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import static network.Utils.ByteConverter.bytesToShort;
/**
* TCP client which receives packets/messages from a race data source
* (e.g., mock source, official source), and exposes them to any observers.
*/
public class VisualiserInput implements Runnable {
///Timestamp of the last heartbeat.
/**
* Timestamp of the last heartbeat.
*/
private long lastHeartbeatTime = -1;
///Sequence number of the last heartbeat.
/**
* Sequence number of the last heartbeat.
*/
private long lastHeartbeatSequenceNum = -1;
///The socket that we have connected to.
private Socket connectionSocket;
///The last RaceStatus message received.
private RaceStatus raceStatus;
/**
* The socket that we have connected to.
*/
private Socket connectionSocket;
///A map of the last BoatStatus message received, for each boat.
private final Map<Integer, BoatStatus> boatStatusMap = new HashMap<>();
///A map of the last BoatLocation message received, for each boat.
private final Map<Integer, BoatLocation> boatLocationMap = new HashMap<>();
/**
* InputStream (from the socket).
*/
private DataInputStream inStream;
///The last AverageWind message received.
private AverageWind averageWind;
///The last CourseWinds message received.
private CourseWinds courseWinds;
/**
* An object containing the set of latest messages to write to.
* Every server frame, VisualiserInput reads messages from its inputStream, and write them to this.
*/
private LatestMessages latestMessages;
///A map of the last MarkRounding message received, for each boat.
private final Map<Integer, MarkRounding> markRoundingMap = new HashMap<>();
///InputStream (from the socket).
private DataInputStream inStream;
/**
* Ctor.
* @param socket Socket from which we will receive race data.
* @param course TODO comment?
* @throws IOException If there is something wrong with the socket's input stream.
*/
public VisualiserInput(Socket socket, StreamedCourse course) throws IOException {
public VisualiserInput(Socket socket) throws IOException {
this.connectionSocket = socket;
//We wrap a DataInputStream around the socket's InputStream because it has the stream.readFully(buffer) function, which is a blocking read until the buffer has been filled.
this.inStream = new DataInputStream(connectionSocket.getInputStream());
this.course = course;
this.latestMessages = new LatestMessages();
this.lastHeartbeatTime = System.currentTimeMillis();
}
/**
* Provides StreamedCourse container for fixed course data.
* @return Course for current VisualiserInput instance.
* @see seng302.Mock.StreamedCourse
*/
public StreamedCourse getCourse() {
return course;
}
/**
* Returns the last boat location message associated with the given boat source ID.
* @param sourceID Unique global identifier for the boat.
* @return The most recent location message.
*/
public BoatLocation getBoatLocationMessage(int sourceID) {
return boatLocationMap.get(sourceID);
}
public BoatStatus getBoatStatusMessage(int sourceID) {
return boatStatusMap.get(sourceID);
}
/**
* Calculates the time since last heartbeat, in milliseconds.
@ -94,63 +90,8 @@ public class VisualiserInput implements Runnable {
return (now - lastHeartbeatTime);
}
/**
* Returns the boat locations map. Maps from Integer (Boat ID) to BoatLocation.
* @return Map of boat locations.
*/
public Map<Integer, BoatLocation> getBoatLocationMap() {
return boatLocationMap;
}
/**
* Gets the status of the race.
* @return The status of the race.
*/
public RaceStatus getRaceStatus() {
return raceStatus;
}
/**
* Returns the boat statuses map. Maps from Integer (Boat ID) to BoatStatus.
* @return Map of boat statuses.
*/
public Map<Integer, BoatStatus> getBoatStatusMap() {
return boatStatusMap;
}
/**
* Returns the average wind of the race.
* @return Average wind in the race.
*/
public AverageWind getAverageWind() {
return averageWind;
}
/**
* Returns winds in the course.
* @return Winds that are in the course.
*/
public CourseWinds getCourseWinds() {
return courseWinds;
}
/**
* Returns the mark roundings map. Maps from Integer (Boat ID) to MarkRounding.
* @return Map of mark roundings.
*/
public Map<Integer, MarkRounding> getMarkRoundingMap() {
return markRoundingMap;
}
/**
* Sets the wind direction for the current course.
* @param direction The new wind direction for the course.
*/
private void setCourseWindDirection(double direction) {
this.course.setWindDirection(direction);
}
/**
* Reads and returns the next message as an array of bytes from the socket. Use getNextMessage() to get the actual message object instead.
* @return Encoded binary message bytes.
@ -258,15 +199,14 @@ public class VisualiserInput implements Runnable {
}
//Continue to the next loop iteration/message.
continue;
}/*
//Add it to message queue.
this.messagesReceivedQueue.add(message);*/
}
//Checks which message is being received and does what is needed for that message.
switch (message.getType()) {
//Heartbeat.
if (message instanceof Heartbeat) {
case HEARTBEAT: {
Heartbeat heartbeat = (Heartbeat) message;
//Check that the heartbeat number is greater than the previous value, and then set the last heartbeat time.
@ -276,150 +216,138 @@ public class VisualiserInput implements Runnable {
//System.out.println("HeartBeat Message! " + lastHeartbeatSequenceNum);
}
}
//RaceStatus.
else if (message instanceof RaceStatus) {
case RACESTATUS: {
RaceStatus raceStatus = (RaceStatus) message;
//System.out.println("Race Status Message");
this.raceStatus = raceStatus;
for (BoatStatus boatStatus: this.raceStatus.getBoatStatuses()) {
this.boatStatusMap.put(boatStatus.getSourceID(), boatStatus);
this.latestMessages.setRaceStatus(raceStatus);
for (BoatStatus boatStatus : raceStatus.getBoatStatuses()) {
this.latestMessages.setBoatStatus(boatStatus);
}
setCourseWindDirection(raceStatus.getScaledWindDirection());
}
//DisplayTextMessage.
/*else if (message instanceof DisplayTextMessage) {
case DISPLAYTEXTMESSAGE: {
//System.out.println("Display Text Message");
//No decoder for this.
}*/
}
//XMLMessage.
else if (message instanceof XMLMessage) {
case XMLMESSAGE: {
XMLMessage xmlMessage = (XMLMessage) message;
//System.out.println("XML Message!");
Platform.runLater(()-> {
if (xmlMessage.getXmlMsgSubType() == XMLMessage.XMLTypeRegatta) {
//System.out.println("Setting Regatta");
try {
course.setRegattaXMLReader(new RegattaXMLReader(xmlMessage.getXmlMessage()));
}
//TODO REFACTOR should put all of these exceptions behind a RegattaXMLReaderException.
catch (IOException | SAXException | ParserConfigurationException e) {
System.err.println("Error creating RegattaXMLReader: " + e.getMessage());
//Continue to the next loop iteration/message.
}
} else if (xmlMessage.getXmlMsgSubType() == XMLMessage.XMLTypeRace) {
//System.out.println("Setting Course");
try {
course.setStreamedCourseXMLReader(new StreamedCourseXMLReader(xmlMessage.getXmlMessage()));
}
//TODO REFACTOR should put all of these exceptions behind a StreamedCourseXMLReaderException.
catch (IOException | SAXException | ParserConfigurationException | StreamedCourseXMLException e) {
System.err.println("Error creating StreamedCourseXMLReader: " + e.getMessage());
//Continue to the next loop iteration/message.
}
this.latestMessages.setXMLMessage(xmlMessage);
} else if (xmlMessage.getXmlMsgSubType() == XMLMessage.XMLTypeBoat) {
//System.out.println("Setting Boats");
try {
course.setBoatXMLReader(new BoatXMLReader(xmlMessage.getXmlMessage()));
}
//TODO REFACTOR should put all of these exceptions behind a BoatXMLReaderException.
catch (IOException | SAXException | ParserConfigurationException e) {
System.err.println("Error creating BoatXMLReader: " + e.getMessage());
//Continue to the next loop iteration/message.
}
}
});
}
//RaceStartStatus.
else if (message instanceof RaceStartStatus) {
case RACESTARTSTATUS: {
//System.out.println("Race Start Status Message");
}
//YachtEventCode.
/*else if (message instanceof YachtEventCode) {
YachtEventCode yachtEventCode = (YachtEventCode) message;
case YACHTEVENTCODE: {
//YachtEventCode yachtEventCode = (YachtEventCode) message;
//System.out.println("Yacht Event Code!");
//No decoder for this.
}*/
}
//YachtActionCode.
/*else if (message instanceof YachtActionCode) {
YachtActionCode yachtActionCode = (YachtActionCode) message;
case YACHTACTIONCODE: {
//YachtActionCode yachtActionCode = (YachtActionCode) message;
//System.out.println("Yacht Action Code!");
//No decoder for this.
// No decoder for this.
}
}*/
//ChatterText.
/*else if (message instanceof ChatterText) {
ChatterText chatterText = (ChatterText) message;
case CHATTERTEXT: {
//ChatterText chatterText = (ChatterText) message;
//System.out.println("Chatter Text Message!");
//No decoder for this.
}*/
}
//BoatLocation.
else if (message instanceof BoatLocation) {
case BOATLOCATION: {
BoatLocation boatLocation = (BoatLocation) message;
//System.out.println("Boat Location!");
if (this.boatLocationMap.containsKey(boatLocation.getSourceID())) {
BoatLocation existingBoatLocation = this.latestMessages.getBoatLocationMap().get(boatLocation.getSourceID());
if (existingBoatLocation != null) {
//If our boatlocation map already contains a boat location message for this boat, check that the new message is actually for a later timestamp (i.e., newer).
if (boatLocation.getTime() > this.boatLocationMap.get(boatLocation.getSourceID()).getTime()){
if (boatLocation.getTime() > existingBoatLocation.getTime()) {
//If it is, replace the old message.
this.boatLocationMap.put(boatLocation.getSourceID(), boatLocation);
this.latestMessages.setBoatLocation(boatLocation);
}
}else{
} else {
//If the map _doesn't_ already contain a message for this boat, insert the message.
this.boatLocationMap.put(boatLocation.getSourceID(), boatLocation);
this.latestMessages.setBoatLocation(boatLocation);
}
}
//MarkRounding.
else if (message instanceof MarkRounding) {
case MARKROUNDING: {
MarkRounding markRounding = (MarkRounding) message;
//System.out.println("Mark Rounding Message!");
if (this.markRoundingMap.containsKey(markRounding.getSourceID())) {
MarkRounding existingMarkRounding = this.latestMessages.getMarkRoundingMap().get(markRounding.getSourceID());
if (existingMarkRounding != null) {
//If our markRoundingMap already contains a mark rounding message for this boat, check that the new message is actually for a later timestamp (i.e., newer).
if (markRounding.getTime() > this.markRoundingMap.get(markRounding.getSourceID()).getTime()){
if (markRounding.getTime() > existingMarkRounding.getTime()) {
//If it is, replace the old message.
this.markRoundingMap.put(markRounding.getSourceID(), markRounding);
this.latestMessages.setMarkRounding(markRounding);
}
}else{
} else {
//If the map _doesn't_ already contain a message for this boat, insert the message.
this.markRoundingMap.put(markRounding.getSourceID(), markRounding);
this.latestMessages.setMarkRounding(markRounding);
}
}
//CourseWinds.
else if (message instanceof CourseWinds) {
case COURSEWIND: {
//System.out.println("Course Wind Message!");
this.courseWinds = (CourseWinds) message;
CourseWinds courseWinds = (CourseWinds) message;
this.latestMessages.setCourseWinds(courseWinds);
}
//AverageWind.
else if (message instanceof AverageWind) {
case AVGWIND: {
//System.out.println("Average Wind Message!");
this.averageWind = (AverageWind) message;
AverageWind averageWind = (AverageWind) message;
this.latestMessages.setAverageWind(averageWind);
}
//Unrecognised message.
else {
default: {
System.out.println("Broken Message!");
}
}
}
}

@ -5,7 +5,7 @@ import com.github.bfsmith.geotimezone.TimeZoneResult;
import javafx.animation.AnimationTimer;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import seng302.GPSCoordinate;
import shared.model.GPSCoordinate;
import java.time.Duration;
import java.time.LocalDateTime;
@ -18,9 +18,9 @@ import java.util.Date;
/**
* This class is used to implement a clock which keeps track of and
* displays times relevant to a race. This is displayed on the
* {@link seng302.Model.ResizableRaceCanvas ResizableRaceCanvas} via the
* {@link seng302.Controllers.RaceController RaceController} and the
* {@link seng302.Controllers.StartController StartController}.
* {@link ResizableRaceCanvas} via the
* {@link visualiser.Controllers.RaceController} and the
* {@link visualiser.Controllers.StartController}.
*/
public class RaceClock implements Runnable {
private long lastTime;

@ -9,6 +9,7 @@ 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 shared.dataInput.BoatDataSource;
import shared.dataInput.RaceDataSource;
@ -30,9 +31,6 @@ import java.util.Map;
public class VisualiserRace extends Race {
//TODO replace with LatestMessages
private final VisualiserInput visualiserInput;
/**
* An observable list of boats in the race.
*/
@ -49,9 +47,17 @@ public class VisualiserRace extends Race {
public VisualiserRace(BoatDataSource boatDataSource, RaceDataSource raceDataSource, RegattaDataSource regattaDataSource, List<Color> colors, VisualiserInput visualiserInput, RaceController controller) {
/**
* Constructs a race object with a given RaceDataSource, BoatDataSource, and RegattaDataSource and receives events from LatestMessages.
* @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 colors A collection of colors used to assign a color to each boat.
* @param latestMessages The LatestMessages to send events to.
*/
public VisualiserRace(BoatDataSource boatDataSource, RaceDataSource raceDataSource, RegattaDataSource regattaDataSource, List<Color> colors, LatestMessages latestMessages, RaceController controller) {
super(boatDataSource, raceDataSource, regattaDataSource);
super(boatDataSource, raceDataSource, regattaDataSource, latestMessages);
this.boats = FXCollections.observableArrayList(this.generateVisualiserBoats(boatDataSource.getBoats(), raceDataSource.getParticipants(), colors));
@ -59,10 +65,8 @@ public class VisualiserRace extends Race {
this.boatMarkers = FXCollections.observableArrayList(boatDataSource.getMarkerBoats().values());
this.controller = controller;
this.visualiserInput = visualiserInput;
}
@ -327,16 +331,16 @@ public class VisualiserRace extends Race {
//Update racing boats.
updateBoats(boats, visualiserInput.getBoatLocationMap(), visualiserInput.getBoatStatusMap());
updateBoats(boats, latestMessages.getBoatLocationMap(), latestMessages.getBoatStatusMap());
//And their positions (e.g., 5th).
updateBoatPositions(boats);
//Update marker boats.
updateMarkers(boatMarkers, visualiserInput.getBoatLocationMap(), visualiserInput.getBoatStatusMap());
updateMarkers(boatMarkers, latestMessages.getBoatLocationMap(), latestMessages.getBoatStatusMap());
//Update race status.
updateRaceStatus(visualiserInput.getRaceStatus());
updateRaceStatus(latestMessages.getRaceStatus());
//TODO tidy this circular dependency up

Loading…
Cancel
Save