Merge branch 'master' into story_61

Also got client-server handshake working. Multiple clients can connect and control their own boat.
The client's boat has a larger black triangle drawn around it. Probably needs tidying up.
Added isClientBoat boolean to VisualiserBoat.

WindGenerator is now an interface, implemented by ConstantWindGenerator (useful for testing) and RandomWindGenerator. Added ConstantWindGeneratorTest.
RandomWindGenerator was formerly WindGenerator.
The WindGenerator is passed in to MockRace.

CommandFactory throws a CommandConstructionException if it cannot create a command.

MessageSerialiser and MessageDeserialiser correctly terminate on error.

Readded VisualiserInput's switch statement in the main loop, pending a refactor.

Removed the sleep statement from LobbyController - it was blocking javaFX thread.

Lobby.fxml: moved buttons and text input into grid panes, so they don't break when you resize screen.

Added some test object creation functions in several test classes (MockRaceTest.createMockRace(), BoatXMLReaderTest.createBoatDataSource(), etc...).

#story[1095]
This commit is contained in:
fjc40
2017-08-10 23:49:21 +12:00
45 changed files with 1320 additions and 556 deletions
@@ -22,16 +22,14 @@ public class Ping implements Runnable {
this.rc = rc;
}
public boolean pingPort(){
//TODO the connection needs to be moved to its own thread, so it doesn't block fx thread.
public boolean pingPort() {
InetSocketAddress i = new InetSocketAddress(hostname, port);
try (Socket s = new Socket()){
s.connect(i, 750);//TODO this should be at least a second or two, once moved to its own thread
s.connect(i, 1500);
s.shutdownInput();
s.shutdownOutput();
s.close();
rc.statusProperty().set("Ready");
//System.out.println(String.valueOf(s.isClosed()));
return true;
} catch (IOException e) {
rc.statusProperty().set("Offline");
@@ -314,6 +314,10 @@ public class ResizableRaceCanvas extends ResizableCanvas {
//The position may be null if we haven't received any BoatLocation messages yet.
if (boat.getCurrentPosition() != null) {
if (boat.isClientBoat()) {
drawClientBoat(boat);
}
//Convert position to graph coordinate.
GraphCoordinate pos = this.map.convertGPS(boat.getCurrentPosition());
@@ -343,6 +347,39 @@ public class ResizableRaceCanvas extends ResizableCanvas {
}
/**
* Draws extra decorations to show which boat has been assigned to the client.
* @param boat The client's boat.
*/
private void drawClientBoat(VisualiserBoat boat) {
//Convert position to graph coordinate.
GraphCoordinate pos = this.map.convertGPS(boat.getCurrentPosition());
//The x coordinates of each vertex of the boat.
double[] x = {
pos.getX() - 12,
pos.getX(),
pos.getX() + 12 };
//The y coordinates of each vertex of the boat.
double[] y = {
pos.getY() + 24,
pos.getY() - 24,
pos.getY() + 24 };
//The above shape is essentially a triangle 24px wide, and 48 long.
//Draw the boat.
gc.setFill(Color.BLACK);
gc.save();
rotate(boat.getBearing().degrees(), pos.getX(), pos.getY());
gc.fillPolygon(x, y, 3);
gc.restore();
}
/**
* Draws the wake for a given boat.
@@ -1,12 +1,7 @@
package visualiser.model;
import mock.app.MockOutput;
import mock.enums.ConnectionStateEnum;
import mock.exceptions.SourceIDAllocationException;
import mock.model.HeartBeatService;
import mock.model.SourceIdAllocator;
import mock.model.commandFactory.CompositeCommand;
import network.Messages.AC35Data;
import network.Messages.Enums.JoinAcceptanceEnum;
import network.Messages.Enums.MessageType;
@@ -19,7 +14,6 @@ import network.StreamRelated.MessageSerialiser;
import shared.exceptions.HandshakeException;
import visualiser.app.VisualiserInput;
import visualiser.gameController.ControllerClient;
import visualiser.gameController.ControllerServer;
import java.io.IOException;
import java.net.Socket;
@@ -38,6 +32,11 @@ public class ServerConnection implements Runnable {
*/
private Socket socket;
/**
* The source ID that has been allocated to the client.
*/
private int allocatedSourceID = 0;
/**
* Latest snapshot of the race, received from the server.
@@ -106,6 +105,9 @@ public class ServerConnection implements Runnable {
new Thread(messageSerialiser, "ServerConnection()->MessageSerialiser thread " + messageSerialiser).start();
new Thread(messageDeserialiser, "ServerConnection()->MessageDeserialiser thread " + messageDeserialiser).start();
this.controllerClient = new ControllerClient(outputQueue);
}
@@ -135,25 +137,23 @@ public class ServerConnection implements Runnable {
connectionState = ConnectionStateEnum.WAITING_FOR_HANDSHAKE;
sendJoinAcceptanceMessage(RequestToJoinEnum.PARTICIPANT);
sendRequestToJoinMessage(RequestToJoinEnum.PARTICIPANT);
JoinAcceptance joinAcceptance = waitForJoinAcceptance();
int allocatedSourceID = 0;
//If we join successfully...
if (joinAcceptance.getAcceptanceType() == JoinAcceptanceEnum.JOIN_SUCCESSFUL) {
allocatedSourceID = joinAcceptance.getSourceID();
//TODO need to do something with the ID - maybe flag the correct visualiser boat as being the client's boat?
this.allocatedSourceID = joinAcceptance.getSourceID();
this.controllerClient = new ControllerClient(inputQueue);
//new Thread(controllerClient, "ServerConnection.run()->ControllerClient thread " + controllerClient).start();
}
this.visualiserInput = new VisualiserInput(latestMessages, outputQueue);
this.visualiserInput = new VisualiserInput(latestMessages, inputQueue);
new Thread(visualiserInput, "ServerConnection.run()->VisualiserInput thread " + visualiserInput).start();
@@ -200,7 +200,7 @@ public class ServerConnection implements Runnable {
* @param requestType The type of request to send
* @throws HandshakeException Thrown if the thread is interrupted while placing message on the outgoing message queue.
*/
private void sendJoinAcceptanceMessage(RequestToJoinEnum requestType) throws HandshakeException {
private void sendRequestToJoinMessage(RequestToJoinEnum requestType) throws HandshakeException {
//Send them the source ID.
RequestToJoin requestToJoin = new RequestToJoin(requestType);
@@ -217,12 +217,27 @@ public class ServerConnection implements Runnable {
/**
* Determines whether or not this connection is still alive.
* This is based off whether the {@link MessageSerialiser} is still alive.
* This is based off whether the {@link MessageDeserialiser} is still alive.
* @return True if it is alive, false otherwise.
*/
public boolean isAlive() {
return messageSerialiser.isRunning();
return messageDeserialiser.isRunning();
}
/**
* Returns the controller client, which writes BoatAction messages to the outgoing queue.
* @return The ControllerClient.
*/
public ControllerClient getControllerClient() {
return controllerClient;
}
/**
* Returns the source ID that has been allocated to the client.
* @return Source ID allocated to the client. 0 if it hasn't been allocated.
*/
public int getAllocatedSourceID() {
return allocatedSourceID;
}
}
@@ -57,6 +57,11 @@ public class VisualiserBoat extends Boat {
*/
private static final double wakeScale = 5;
/**
* If true then this boat has been allocated to the client.
*/
private boolean isClientBoat = false;
@@ -214,4 +219,21 @@ public class VisualiserBoat extends Boat {
return " -";
}
}
/**
* Returns whether or not this boat has been assigned to the client.
* @return True if this is the client's boat.
*/
public boolean isClientBoat() {
return isClientBoat;
}
/**
* Sets whether or not this boat has been assigned to the client.
* @param clientBoat True if this is the client's boat.
*/
public void setClientBoat(boolean clientBoat) {
isClientBoat = clientBoat;
}
}
@@ -347,13 +347,16 @@ public class VisualiserRace extends Race implements Runnable {
@Override
public void handle(long arg0) {
//Calculate the frame period.
long currentFrameTime = System.currentTimeMillis();
long framePeriod = currentFrameTime - lastFrameTime;
//Update race status.
updateRaceStatus(latestMessages.getRaceStatus());
if (latestMessages.getRaceStatus() != null) {
updateRaceStatus(latestMessages.getRaceStatus());
}
//Update racing boats.